What the deduplication work and the two calculation fixes actually did to the books, measured day by day against production's own summaries.
Both arms are computed on the same deduplicated data, so this isolates what the calculation fixes are worth. Figures exclude the ten deactivated duplicate clients, which the plan says to exclude from reporting; including them the shape is identical (432 → 70 days, $25,622.98 → $1,548.64).
This is the question that matters most for the books, and it has two halves. No day that balanced under production went out of balance — that count is zero. But 237 client-days that were balanced had their line amounts change anyway, and a balance-only view would hide every one of them.
None of those 237 came from the calculation fixes. Both fixes are provably inert on balanced days — see the decomposition below. They came from the data being corrected underneath.
| Outcome, production → final | Client-days | Reading |
|---|---|---|
| Out of balance → balanced | 309 | the intended repair |
| Balanced → out of balance | 0 | nothing was broken |
| Balanced → balanced, numbers changed | 237 | amounts moved, balance held |
| Balanced → balanced, identical | 1,652 | untouched |
| Still out of balance | 44 | residual, see below |
| Production summaries in window | 2,242 | of 6,300 client-day slots |
| Pattern | Days | Cause |
|---|---|---|
| Every category 0.00 → real values | ~100 | production held an all-zero summary for a day that had orders |
| Card Payments ↔ Fees reallocation | ~135 | the payout arrived, so the processing fee is now known and booked separately |
| Service Charges line appears | 13 | R2, on days that were already out of balance |
The second pattern is worth reading carefully: Card Payments falls by exactly what Fees gains, so the day stays balanced while the split between the two lines changes. That is a data-freshness effect from re-importing, not a change in how anything is calculated. Three real rows:
| Client | Date | Line | Before | After |
|---|---|---|---|---|
| NGAN | 2026-08-12 | Card Payments | 1,237.23 | 1,207.64 |
| Fees | 0.00 | 29.59 | ||
| NGVC | 2026-08-10 | Card Payments | 2,025.03 | 2,074.63 |
| Fees | 49.60 | 0.00 | ||
| NGGP | 2026-08-13 | every line | 0.00 | real figures |
NGAN moves $29.59 out of Card Payments and into Fees; NGVC moves $49.60 the other way. Both stay balanced to the cent. NGGP is the first pattern — production held an all-zero summary for a day with $2,478.44 of card payments, $1,148.11 of gyros and pitas, and so on.
Applied in sequence, each measured against the state before it. Percentages of a 3,040 client-day population.
| Outcome | Client-days |
|---|---|
| Unchanged | 1,581 |
| Numbers changed | 547 |
| Out of balance → balanced | 56 |
| Balanced → out of balance | 19 |
| Balanced → balanced, numbers changed | 237 |
The data work is the only step that unbalances anything: 19 days go out of balance purely from correcting the data, mostly where a refund that had been sitting under the twin client now lands on the right one. The two calculation fixes then absorb all 19, which is why the end-to-end figure is zero.
| Outcome | Client-days | Share |
|---|---|---|
| Unchanged | 2,941 | 96.7% |
| Out of balance → balanced | 86 | 2.8% |
| Balanced → out of balance | 0 | — |
| Balanced → balanced, numbers changed | 0 | — |
| Still out of balance, but closer | 13 | 0.4% |
| Days touched · dollars moved | 99 | $973.79 |
| Outcome | Client-days | Share |
|---|---|---|
| Unchanged | 2,760 | 90.8% |
| Out of balance → balanced | 276 | 9.1% |
| Balanced → out of balance | 0 | — |
| Balanced → balanced, numbers changed | 0 | — |
| Still out of balance, but closer | 4 | 0.1% |
| Days touched · dollars moved | 280 | $23,729.45 |
Across 3,040 client-days, neither R1 nor R2 changed a single number on a day that was already balanced. Every day they touched was already out of balance. That is the strongest available evidence that they cannot quietly restate correct books — and it is why R2 carries the larger dollar figure without carrying larger risk.
Each fix is easiest to check on a single order. In every case below the day's imbalance equals the fix amount exactly, which is what you would expect if the fix is booking something real that was previously booked nowhere.
| Field | Value |
|---|---|
| Order | square/order/NGLK-SM-OxSX9gpXJV394qqT8mnBGypUwKNZY |
:sales-order/tip | −60.00 |
:sales-order/total | −60.00 |
:sales-order/charges | 0 — no tender at all |
| NGLK 2026-08-04 imbalance | −60.00 → 0.00 |
A guest's tip was handed back. The reversal is recorded on the order, but get-tip reaches tips by joining through :sales-order/charges, and a return-only order has no charge to join through — so the −$60.00 was invisible and the day credited a tip that no longer existed. The other four largest R1 repairs are the same shape: NGFF 08-01 (−$40.00), NGSG 07-29 (−$40.00), NGNV 08-09 (−$35.00), NGJS 07-25 (−$35.00).
| Field | Value |
|---|---|
| Order | square/order/NTPT-PT-KrMZzcon1cpQEJUyetErkBIpcdEZY |
:sales-order/service-charge | 427.10 |
:sales-order/total | 3,198.78 |
| NTPT 2026-08-06 imbalance | +427.10 → 0.00 |
The $427.10 is inside the card tender the customer paid, so it lands on the debit side — but nothing credited it, leaving the day short by exactly that amount. Next largest: NGFO 07-16 ($275.23), NGRN 08-06 ($236.96), N-30008 07-24 ($229.42), N-30003 07-18 ($225.00).
| Client | Date | After data fix | After R1 + R2 |
|---|---|---|---|
| NGSF | 2026-08-13 | +137.99 | 0.00 |
| NGRY | 2026-08-13 | +103.26 | 0.00 |
| NGDS | 2026-08-13 | +100.52 | 0.00 |
| NGDV | 2026-08-13 | +100.52 | 0.00 |
| N-30007 | 2026-08-13 | +86.00 | 0.00 |
| N-30003 | 2026-08-13 | +83.53 | 0.00 |
| NGSC | 2026-08-13 | +75.00 | 0.00 |
| NGGH | 2026-08-13 | +75.00 | 0.00 |
| NGDG | 2026-08-13 | +70.00 | 0.00 |
| NGDU | 2026-08-13 | +70.00 | 0.00 |
| All 19 days | unbalanced | balanced | |
These are days production held an empty summary for — trivially balanced at zero. Filling them in with real figures exposes the same two calculation gaps every other day had, and both fixes then close them. Not one of the 19 survives as a regression. Their clustering on 08-13 is expected: it is the most recent day, so it is the one production had least chance to compute.
Of 6,300 client-day slots in the window, production held a summary for only 2,242. The remaining 4,058 had none at all — nothing to compare against, and nothing an accountant could have looked at.
They are not evenly spread. Eight consecutive days show 210 missing summaries each, which is every client in the database:
| Dates | Missing per day | Reading |
|---|---|---|
| Jul 15 – Jul 29 | 117 | clients with no summary that day |
| Jul 30 – Aug 6 | 210 | every client — a total coverage hole |
| Aug 7 – Aug 13 | 89 | clients with no summary that day |
The plan predicted a global gap at 2026-07-30 → 08-06 from reading the scheduler, which only looks back seven days and so can never backfill a hole older than that. This measurement found the same eight days independently, from the data. Every one of those 1,680 client-days now has a summary.
Five client-days above the ten-cent materiality threshold, totalling $404.04. Everything else — 62 days — comes to $1.62, with the largest single day at nine cents.
| Client | Date | Variance | Explanation |
|---|---|---|---|
| NGBK | 2026-08-06 | +$299.42 | tender exceeds order totals by exactly this much, in the source data |
| NGDA | 2026-08-01 | −$50.00 | auto-gratuity booked as a service charge |
| NGEB | 2026-08-10 | −$25.00 | ezCater fee semantics predicted |
| NGEB | 2026-07-29 | −$20.00 | ezCater fee semantics predicted |
| NGPS | 2026-08-12 | +$9.62 | unexplained predicted |
The ten-cent threshold separates register rounding from real variance with nothing sitting near the boundary — the largest sub-threshold day is 9.00¢ and the smallest material one is $9.62, two orders of magnitude apart.
NGBK 2026-08-06, traced. Summing that client-day's orders directly: tender totals $6,358.99 while the orders' own totals come to $6,059.57 — a difference of $299.42, exactly the imbalance. Square recorded more payment than the orders account for. Three explanations were checked and ruled out: the day has no refund lines at all, no order carries two charges for one payment id, and the nine orders lacking line items carry zero tender. This is a source-data discrepancy, not a calculation defect, and not something either fix should paper over.
as-of — not a re-simulation of them. Datomic keeps every past value, so the comparison is against exactly what was there.fixed + untendered tip + service charges. That identity was checked against a from-scratch baseline recomputation on 20 random client-days and agreed on every one.Re-keying a charge that has two parent orders duplicates the tender, because the other client's import then matches neither key and creates a second charge which cardinality-many appends. The migration must split shared charges first. Separately, remove-voided-orders retracts orders, and 63% of the contended clients' charges have two parents, so a retraction there deletes the other client's payment.
Every figure here comes from the restored database and can be re-derived from it. The comparison tool is committed as auto-ap.jobs.compare-sales-summaries.
;; connect to the restored backup (read-only is fine) (def conn (d/connect "datomic:dev://localhost:4337/integreat-prod-restore")) ;; the whole comparison: production's own summaries vs now (require '[auto-ap.jobs.compare-sales-summaries :as cs]) (def r (cs/compare-against 209608347 (clj-time.core/date-time 2026 7 15) (clj-time.core/date-time 2026 8 14))) (:tally r) ;; identical / numbers-changed / added (:balance-transitions r) ;; the four-way counts (count (:previously-balanced-and-changed r)) ;; => 237 ;; what actually moved on one of those days (cs/line-diff (first (:previously-balanced-and-changed r))) ;; the single order behind the R1 example (d/pull (d/db conn) '[*] [:sales-order/external-id "square/order/NGLK-SM-OxSX9gpXJV394qqT8mnBGypUwKNZY"]) ;; the single order behind the R2 example (d/pull (d/db conn) '[*] [:sales-order/external-id "square/order/NTPT-PT-KrMZzcon1cpQEJUyetErkBIpcdEZY"])
The unit tests covering both fixes and the re-key run with:
lein test auto-ap.jobs.sales-summaries-test auto-ap.square.core3-test
;; => 15 tests, 26 assertions, 0 failures
Note that the restore currently has Phase 0 applied and the twins deactivated. Re-running the comparison after re-activating them will move the contended clients' figures; everything outside those ten client pairs is unaffected.