docs(sales-summaries): single report, rebuilt on a pristine restore

Deletes and restores the database to backup point 209608347, then runs the whole
pipeline on untouched production data with the corrected split: deactivate the
duplicate client per location, give every order its own payment record,
client-scope refunds, payouts and cash-drawer shifts, import live from Square,
and recompute ninety days at each stage.

  production today   1,280 days off, 85.34% clean, $75,228.78
  + deduplication    1,087 days off, 87.55% clean, $63,764.92
  + refunded tips      800 days off, 90.84% clean, $60,619.21
  + service charges    108 days off, 98.76% clean,  $1,995.36

1,172 client-days brought into balance, 0 knocked out. Neither arithmetic fix
altered a single figure on a day that already balanced. Excluding the ten
deactivated duplicates: 1,217 days and $69,995.61 down to 105 days and $852.38.

The split does what it was missing before: 189,167 payments re-keyed, 77,599
copied so each order owns one, and payments with two parent orders went 11,469
to 0 — holding at 0 after a live import across 48,362 orders. That closes the
component-cascade risk in remove-voided-orders.

Report rewritten for both audiences: what each fault meant for the books, then
the mechanism for engineers, with worked line-level examples, the verification
checks, the residual, and the open decisions. Includes the double-scoping bug
this work introduced and how measurement caught it.

23 tests, 55 assertions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 13:49:44 -07:00
parent 088dade112
commit 011d515033

View File

@@ -1,17 +1,9 @@
<title>Ninety-Day Reconciliation</title>
<style>
:root {
--paper: #F6F8F7;
--card: #FFFFFF;
--ink: #141F1D;
--ink-soft: #4A5C58;
--ink-faint: #7C8D89;
--rule: #DCE4E1;
--accent: #0E5B57;
--accent-soft: #E3EFED;
--good: #1A6B49;
--bad: #A03B26;
--warn: #8A6410;
--paper: #F6F8F7; --card: #FFFFFF; --ink: #141F1D; --ink-soft: #4A5C58;
--ink-faint: #7C8D89; --rule: #DCE4E1; --accent: #0E5B57; --accent-soft: #E3EFED;
--good: #1A6B49; --bad: #A03B26; --warn: #8A6410;
--shadow: 0 1px 2px rgba(20,31,29,.06), 0 8px 24px rgba(20,31,29,.05);
}
@media (prefers-color-scheme: dark) {
@@ -28,47 +20,36 @@
--good: #5FBE8C; --bad: #E08A72; --warn: #D6AC55;
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.3);
}
* { box-sizing: border-box; }
body {
background: var(--paper); color: var(--ink);
body { background: var(--paper); color: var(--ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
font-size: 16px; line-height: 1.6; margin: 0; padding: 0 20px 96px;
}
font-size: 16px; line-height: 1.6; margin: 0; padding: 0 20px 96px; }
.wrap { max-width: 940px; margin: 0 auto; }
.measure { max-width: 66ch; }
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
.num { font-variant-numeric: tabular-nums; }
.mono { font-family: ui-monospace, "SF Mono", "Cascadia Code", monospace; font-variant-numeric: tabular-nums; }
header.masthead { padding: 72px 0 40px; border-bottom: 2px solid var(--ink); display: flex; flex-direction: column; gap: 14px; }
.eyebrow { font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); font-weight: 600; }
h1 {
font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
font-size: clamp(34px, 5.4vw, 54px); line-height: 1.08; font-weight: 600;
letter-spacing: -.015em; margin: 0; text-wrap: balance;
}
.standfirst { font-size: 19px; color: var(--ink-soft); margin: 0; max-width: 60ch; }
h1 { font-family: Georgia, "Iowan Old Style", serif; font-size: clamp(34px, 5.4vw, 54px);
line-height: 1.08; font-weight: 600; letter-spacing: -.015em; margin: 0; text-wrap: balance; }
.standfirst { font-size: 19px; color: var(--ink-soft); margin: 0; max-width: 62ch; }
.meta { display: flex; flex-wrap: wrap; gap: 10px 28px; font-size: 13px; color: var(--ink-faint); padding-top: 6px; }
.meta b { color: var(--ink-soft); font-weight: 600; }
section { padding-top: 56px; display: flex; flex-direction: column; gap: 20px; }
h2 { font-family: Georgia, "Iowan Old Style", serif; font-size: 27px; font-weight: 600; letter-spacing: -.01em; margin: 0; text-wrap: balance; }
h3 { font-size: 14px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft); font-weight: 700; margin: 0; }
h4 { font-size: 18px; font-weight: 650; margin: 0; letter-spacing: -.01em; }
p { margin: 0; }
.measure p + p { margin-top: 14px; }
.ledger {
display: grid; grid-template-columns: 1fr auto 1fr; align-items: stretch;
border: 1px solid var(--rule); border-radius: 4px; background: var(--card);
box-shadow: var(--shadow); overflow: hidden;
}
.ledger { display: grid; grid-template-columns: 1fr auto 1fr; border: 1px solid var(--rule);
border-radius: 4px; background: var(--card); box-shadow: var(--shadow); overflow: hidden; }
.ledger > div { padding: 26px 28px; display: flex; flex-direction: column; gap: 6px; }
.ledger .arrow {
justify-content: center; align-items: center;
border-left: 1px solid var(--rule); border-right: 1px solid var(--rule);
color: var(--ink-faint); font-size: 22px; padding: 26px 18px; background: var(--accent-soft);
}
.ledger .side-label { font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); font-weight: 600; }
.ledger .arrow { justify-content: center; align-items: center; border-left: 1px solid var(--rule);
border-right: 1px solid var(--rule); color: var(--ink-faint); font-size: 22px; background: var(--accent-soft); }
.side-label { font-size: 12px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); font-weight: 600; }
.figure { font-size: clamp(28px, 4.4vw, 40px); font-weight: 650; line-height: 1.05; letter-spacing: -.02em; }
.figure.after { color: var(--good); }
.subfig { font-size: 14px; color: var(--ink-soft); }
@@ -79,15 +60,9 @@
.stat .l { font-size: 13px; color: var(--ink-soft); }
.stat.zero .k { color: var(--good); }
.step { border-left: 2px solid var(--rule); padding-left: 26px; position: relative; }
.step::before {
content: attr(data-step); position: absolute; left: -13px; top: 2px;
width: 24px; height: 24px; border-radius: 50%;
background: var(--accent); color: var(--paper);
font-size: 12px; font-weight: 700; display: grid; place-items: center;
}
.step h3 { margin-bottom: 8px; }
.step-title { font-size: 20px; font-weight: 650; letter-spacing: -.01em; }
.problem { border-left: 3px solid var(--accent); padding-left: 24px; display: flex; flex-direction: column; gap: 14px; }
.problem.two { border-left-color: var(--warn); }
.problem.three { border-left-color: var(--bad); }
.scroll { overflow-x: auto; border: 1px solid var(--rule); border-radius: 4px; background: var(--card); }
table { border-collapse: collapse; width: 100%; font-size: 14.5px; }
@@ -100,434 +75,288 @@
.bad { color: var(--bad); font-weight: 650; }
.dim { color: var(--ink-faint); }
.callout { background: var(--card); border: 1px solid var(--rule); border-left: 3px solid var(--accent); border-radius: 4px; padding: 20px 24px; display: flex; flex-direction: column; gap: 10px; }
.callout { background: var(--card); border: 1px solid var(--rule); border-left: 3px solid var(--accent);
border-radius: 4px; padding: 20px 24px; display: flex; flex-direction: column; gap: 10px; }
.callout.warn { border-left-color: var(--warn); }
.callout .h { font-weight: 650; }
code { font-family: ui-monospace, "SF Mono", monospace; font-size: .9em; background: var(--accent-soft); padding: 1px 5px; border-radius: 3px; }
pre { margin: 0; padding: 20px; font-size: 13px; line-height: 1.7; white-space: pre; font-family: ui-monospace, "SF Mono", monospace; }
footer { margin-top: 72px; padding-top: 24px; border-top: 1px solid var(--rule); font-size: 13px; color: var(--ink-faint); display: flex; flex-direction: column; gap: 8px; }
ul { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }
.tech { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
font-weight: 700; border: 1px solid var(--accent); border-radius: 3px; padding: 2px 7px; display: inline-block; }
</style>
<div class="wrap">
<header class="masthead">
<div class="eyebrow">Sales summaries · restored production backup</div>
<div class="eyebrow">Sales summaries · measured on a restored production backup</div>
<h1>Ninety-Day Reconciliation</h1>
<p class="standfirst">A true baseline: the job re-run three times over ninety days — production's calculation, then each fix added — so every number below is measured output, not arithmetic.</p>
<p class="standfirst">Three faults were leaving restaurant days out of balance — one in the data, two in the arithmetic. This is what they were, what they cost, and what fixing them is worth, measured by running the real job over ninety days of real trading.</p>
<div class="meta">
<span><b>Window</b> 2026-05-10 → 2026-08-07</span>
<span><b>Client-days</b> <span class="num">8,733</span> with activity</span>
<span><b>Client-days</b> <span class="num">8,733</span></span>
<span><b>Clients</b> <span class="num">106</span></span>
<span><b>Most recent week</b> omitted</span>
<span><b>Nothing in production was changed</b></span>
</div>
</header>
<section>
<div class="ledger">
<div>
<span class="side-label">Baseline</span>
<span class="figure num">$58,531.75</span>
<span class="subfig"><span class="num">1,024</span> days out of balance · <span class="num">87.74%</span> clean</span>
<span class="side-label">Production today</span>
<span class="figure num">$75,228.78</span>
<span class="subfig"><span class="num">1,280</span> days out of balance · <span class="num">85.34%</span> clean</span>
</div>
<div class="arrow" aria-hidden="true"></div>
<div>
<span class="side-label">After both fixes</span>
<span class="figure after num">$852.38</span>
<span class="subfig"><span class="num">105</span> days out of balance · <span class="num">98.74%</span> clean</span>
<span class="side-label">After all three fixes</span>
<span class="figure after num">$1,995.36</span>
<span class="subfig"><span class="num">108</span> days out of balance · <span class="num">98.76%</span> clean</span>
</div>
</div>
<div class="stats">
<div class="stat"><span class="k num">919</span><span class="l">client-days brought into balance</span></div>
<div class="stat"><span class="k num">1,172</span><span class="l">client-days brought into balance</span></div>
<div class="stat zero"><span class="k num">0</span><span class="l">days knocked out of balance</span></div>
<div class="stat zero"><span class="k num">0</span><span class="l">balanced days whose numbers moved</span></div>
<div class="stat"><span class="k num">98.5%</span><span class="l">of the dollar variance removed</span></div>
<div class="stat"><span class="k num">97.3%</span><span class="l">of the variance removed</span></div>
<div class="stat zero"><span class="k num">0</span><span class="l">shared payment records left</span></div>
</div>
<div class="measure">
<p>Figures exclude the ten deactivated duplicate clients, which the plan says to exclude from reporting. Across the full 8,733 client-days including them the shape is the same: 1,087 → 108 days out of balance, $63,764.92 → $1,995.36.</p>
<p><strong>In one sentence:</strong> a day's sales summary should show the money taken and the money earned agreeing to the penny, and on roughly one day in seven it did not — because two clients were fighting over the same records, tips that had been refunded were still counted as income, and service charges customers paid were credited to nothing.</p>
</div>
</section>
<section>
<h2>Why this baseline is different</h2>
<div class="measure">
<p>The earlier thirty-day report derived its baseline arithmetically. This one does not. The job was run three separate times over the same ninety days, against the same data, writing real summaries each time:</p>
<ul>
<li><strong>Run A — baseline.</strong> The service-charge flag cleared on all 210 clients and <code>get-tip</code> restored to its tendered-only form. This is exactly what production calculates today.</li>
<li><strong>Run B — plus R1.</strong> Only <code>get-tip</code> changed.</li>
<li><strong>Run C — plus R2.</strong> The service-charge flag switched on as well.</li>
</ul>
<p>The underlying data — Phase 0, the re-key, the repaired charge references — is identical across all three, so what separates them is the calculation and nothing else. 18,900 summaries were written per run, 56,700 in total.</p>
<p>The most recent week (08-08 → 08-14) is excluded deliberately. The backup was cut mid-evening on the 14th, and the days either side of that are partial, which distorted the earlier window.</p>
<h2>The three problems</h2>
<div class="problem">
<h4>1. Two client records sharing one Square location</h4>
<div class="measure">
<p><strong>For the business:</strong> ten restaurant locations were set up twice in the system, as two separate clients. Both were importing from Square. Because the two records competed for the same payments and refunds, a refund would belong to one client for twenty minutes, then the other — so a day's books could gain or lose a refund depending on nothing but timing. On 2026-07-23 one client's summary was missing a $71.94 refund entirely, and was out of balance by exactly that amount.</p>
<p><span class="tech">technical</span> Sales orders scoped their identifier by client (<code>square/order/&lt;code&gt;-&lt;loc&gt;-&lt;id&gt;</code>), but refunds, card charges, payouts and cash-drawer shifts did not — they used the bare Square id. Those attributes are <code>:db.unique/identity</code>, so both clients' imports resolved to a single entity and the last writer won.</p>
<p>Reading ownership out of the database's own history, this had actually happened to <strong>3,387 refunds, 4,069 payouts and 2,628 cash-drawer shifts</strong>. And it has involved <strong>19 client pairs, of which only 10 are visible in today's configuration</strong> — nine more contended in the past and the configuration has since changed, so no point-in-time check would find them.</p>
</div>
</div>
<div class="problem two">
<h4>2. One payment record owned by two orders</h4>
<div class="measure">
<p><strong>For the business:</strong> the same collision meant a single card payment could be attached to both clients' copies of an order. That is worse than untidy. The nightly import removes orders Square reports as voided, and removing an order also removes its payments — so cancelling one client's order could silently delete the <em>other</em> client's payment, leaving a day showing sales with no money against them.</p>
<p><span class="tech">technical</span> <code>:sales-order/charges</code> is declared <code>:db/isComponent true</code>, so <code>[:db/retractEntity &lt;order&gt;]</code> cascades into the charges. In a 20,000-order sample of the affected clients, <strong>11,469 charges had two parent orders</strong>. This is why <code>remove-voided-orders</code> was left switched off during testing.</p>
</div>
</div>
<div class="problem three">
<h4>3. Tips refunded, and service charges credited nowhere</h4>
<div class="measure">
<p><strong>For the business:</strong> two arithmetic faults, both of which overstated or understated a day.</p>
<ul>
<li><strong>Refunded tips stayed on the books.</strong> When a guest was refunded, the tip came back too — but the summary still counted the original tip as income. On one NGLK day the books credited $482.94 of tips beside a $60.00 refund of that very tip.</li>
<li><strong>Service charges were collected but never earned.</strong> A catering or auto-gratuity charge is inside the card payment the customer makes, so it arrived as money taken — but no line recorded it as money earned. One NTPT order carried $427.10 that was credited to nothing at all; the largest single instance was <strong>$1,344.86</strong> in one day.</li>
</ul>
<p><span class="tech">technical</span> <code>get-tip</code> summed tips by joining through <code>:sales-order/charges</code>, so a return-only order — which has no tender to join through — contributed nothing, while its reversal sat unread on <code>:sales-order/tip</code>. Nothing at all read <code>:sales-order/service-charge</code>.</p>
</div>
</div>
</section>
<section>
<h2>Impact by underlying fix</h2>
<h2>What each fix is worth</h2>
<div class="measure">
<p>Applied in sequence, each measured against the run before it, over 8,350 reportable client-days.</p>
</div>
<div class="step" data-step="1">
<h3>R1</h3>
<div class="step-title">Tips on untendered orders</div>
<p>The job was run over the same ninety days at each stage, writing real summaries every time, so these are measured outcomes rather than estimates.</p>
</div>
<div class="scroll">
<table>
<thead><tr><th>Outcome</th><th class="n">Client-days</th><th class="n">Share</th></tr></thead>
<thead><tr><th>Stage</th><th class="n">Days out of balance</th><th class="n">Clean</th><th class="n">Total variance</th></tr></thead>
<tbody>
<tr><td>Unchanged</td><td class="n">8,054</td><td class="n dim">96.5%</td></tr>
<tr><td>Out of balance → balanced</td><td class="n good">272</td><td class="n dim">3.3%</td></tr>
<tr><td>Balanced → out of balance</td><td class="n good">0</td><td class="n dim"></td></tr>
<tr><td>Balanced → balanced, numbers changed</td><td class="n good">0</td><td class="n dim"></td></tr>
<tr><td>Still out of balance, but closer</td><td class="n">24</td><td class="n dim">0.3%</td></tr>
<tr class="total"><td>Days touched · dollars moved</td><td class="n">296</td><td class="n">$3,623.44</td></tr>
<tr><td>Production today</td><td class="n">1,280</td><td class="n">85.34%</td><td class="n">$75,228.78</td></tr>
<tr><td>+ deduplication</td><td class="n">1,087</td><td class="n">87.55%</td><td class="n">$63,764.92</td></tr>
<tr><td>+ refunded tips</td><td class="n">800</td><td class="n">90.84%</td><td class="n">$60,619.21</td></tr>
<tr class="total"><td>+ service charges</td><td class="n good">108</td><td class="n good">98.76%</td><td class="n good">$1,995.36</td></tr>
</tbody>
</table>
</div>
<div class="step" data-step="2">
<h3>R2</h3>
<div class="step-title">Square service charges, both signs</div>
</div>
<h3>Day-by-day effect of each change</h3>
<div class="scroll">
<table>
<thead><tr><th>Outcome</th><th class="n">Client-days</th><th class="n">Share</th></tr></thead>
<thead><tr><th>Change</th><th class="n">Unchanged</th><th class="n">Into balance</th><th class="n">Out of balance</th><th class="n">Balanced days altered</th><th class="n">Money moved</th></tr></thead>
<tbody>
<tr><td>Unchanged</td><td class="n">7,701</td><td class="n dim">92.2%</td></tr>
<tr><td>Out of balance → balanced</td><td class="n good">647</td><td class="n dim">7.7%</td></tr>
<tr><td>Balanced → out of balance</td><td class="n good">0</td><td class="n dim"></td></tr>
<tr><td>Balanced → balanced, numbers changed</td><td class="n good">0</td><td class="n dim"></td></tr>
<tr><td>Still out of balance, but closer</td><td class="n">2</td><td class="n dim">&lt;0.1%</td></tr>
<tr class="total"><td>Days touched · dollars moved</td><td class="n">649</td><td class="n">$54,775.79</td></tr>
<tr><td>Deduplication</td><td class="n">7,447 <span class="dim">balanced already</span></td><td class="n good">199</td><td class="n bad">6</td><td class="n dim"></td><td class="n dim"></td></tr>
<tr><td>Refunded tips</td><td class="n">8,421</td><td class="n good">287</td><td class="n good">0</td><td class="n good">0</td><td class="n">$3,777.67</td></tr>
<tr><td>Service charges</td><td class="n">8,037</td><td class="n good">692</td><td class="n good">0</td><td class="n good">0</td><td class="n">$58,923.85</td></tr>
<tr class="total"><td>All three, end to end</td><td class="n">7,453</td><td class="n good">1,172</td><td class="n good">0</td><td class="n good">0</td><td class="n"></td></tr>
</tbody>
</table>
</div>
<h3>Both together, full population</h3>
<div class="scroll">
<table>
<thead><tr><th>Outcome</th><th class="n">Client-days</th></tr></thead>
<tbody>
<tr><td>Unchanged</td><td class="n">7,749</td></tr>
<tr><td>Out of balance → balanced</td><td class="n good">979</td></tr>
<tr><td>Balanced → out of balance</td><td class="n good">0</td></tr>
<tr><td>Balanced → balanced, numbers changed</td><td class="n good">0</td></tr>
<tr><td>Still out of balance, but closer</td><td class="n">5</td></tr>
<tr class="total"><td>Days touched · dollars moved</td><td class="n">984 · $62,069.64</td></tr>
</tbody>
</table>
</div>
<div class="callout">
<span class="h">Neither fix touches a healthy day — now confirmed over three times the window</span>
<p>Across 8,733 client-days and 90 days of trading, not one day that balanced under production's calculation was altered by either fix: zero knocked out of balance, and zero whose amounts moved while staying balanced. Every day they touched was already out of balance. The thirty-day run found the same thing; this replicates it on a much larger sample.</p>
<div class="measure">
<p><strong>Neither arithmetic fix touched a day that was already correct.</strong> Across 8,733 client-days, no balanced day was knocked out of balance and no balanced day had a single figure altered — every day they changed was already wrong. Service charges are by far the larger of the two, moving $58,923.85 against the tip fix's $3,777.67.</p>
<p>Deduplication is the one step that puts six days <em>out</em> of balance. That is expected: those days were previously balanced only because a summary was empty, and filling it in exposes the same arithmetic faults every other day had. Both later fixes then close them, which is why the end-to-end figure is zero.</p>
<p>Excluding the ten now-deactivated duplicate records, which should not be reported on at all, the population is 8,350 client-days and the movement is <strong>1,217 days and $69,995.61 down to 105 days and $852.38</strong>.</p>
</div>
</section>
<section>
<h2>What actually changed on the page</h2>
<h2>What it looks like on the page</h2>
<div class="measure">
<p>Both fixes add a single credit line. Nothing else in a summary moves — no sales figure, no
tender, no tax. That is why they can only ever help a day that was already short, and it is
visible in the line items.</p>
<p>Both arithmetic fixes add exactly one credit line. Nothing else in a summary moves — no sales figure, no payment, no tax.</p>
</div>
<h3>R1 — NGLK, 2026-08-04</h3>
<h3>A refunded tip — NGLK, 2026-08-04</h3>
<div class="scroll">
<table>
<thead><tr><th>Line</th><th class="n">Before</th><th class="n">After</th><th class="n">Change</th></tr></thead>
<thead><tr><th>Line</th><th class="n">Before</th><th class="n">After</th></tr></thead>
<tbody>
<tr><td><strong>Tip</strong> (credit)</td><td class="n">482.94</td><td class="n">422.94</td><td class="n bad">60.00</td></tr>
<tr><td class="dim">Card Refunds (credit)</td><td class="n dim">60.00</td><td class="n dim">60.00</td><td class="n dim"></td></tr>
<tr><td class="dim">every other line</td><td class="n dim" colspan="2">unchanged</td><td class="n dim"></td></tr>
<tr><td>Total debits</td><td class="n">10,094.81</td><td class="n">10,094.81</td><td class="n dim"></td></tr>
<tr><td>Total credits</td><td class="n">10,154.81</td><td class="n">10,094.81</td><td class="n">60.00</td></tr>
<tr class="total"><td>Imbalance</td><td class="n bad">60.00</td><td class="n good">0.00</td><td class="n">balanced</td></tr>
<tr><td><strong>Tip</strong></td><td class="n">482.94</td><td class="n">422.94</td></tr>
<tr><td class="dim">Card Refunds</td><td class="n dim">60.00</td><td class="n dim">60.00</td></tr>
<tr><td>Total money taken</td><td class="n">10,094.81</td><td class="n">10,094.81</td></tr>
<tr><td>Total money earned</td><td class="n">10,154.81</td><td class="n">10,094.81</td></tr>
<tr class="total"><td>Out of balance by</td><td class="n bad">60.00</td><td class="n good">0.00</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>Read the two credit lines together and the story is complete: the day already carried a
<strong>$60.00 card refund</strong> — the guest was given their money back, tip included — but the
tip was still credited in full at $482.94, because the reversal lives on an order with no tender
and <code>get-tip</code> only reached tips through tenders. The books claimed $60 of tip income
that had been handed back. The corrected figure, $422.94, matches the refund exactly.</p>
<p>The day already carried a $60.00 card refund — the guest was given their money back, tip included — while the tip line still credited the full $482.94. The corrected figure matches the refund to the penny. The order behind it is <span class="mono">square/order/NGLK-SM-OxSX9gpXJV394qqT8mnBGypUwKNZY</span>: a tip of 60.00 on an order with no payment attached at all.</p>
</div>
<h3>R2 — NTPT, 2026-08-06</h3>
<h3>A service charge — NTPT, 2026-08-06</h3>
<div class="scroll">
<table>
<thead><tr><th>Line</th><th class="n">Before</th><th class="n">After</th><th class="n">Change</th></tr></thead>
<thead><tr><th>Line</th><th class="n">Before</th><th class="n">After</th></tr></thead>
<tbody>
<tr><td><strong>Service Charges</strong> (credit)</td><td class="n bad">absent</td><td class="n">427.10</td><td class="n good">+427.10</td></tr>
<tr><td class="dim">Card Payments (debit)</td><td class="n dim">4,975.89</td><td class="n dim">4,975.89</td><td class="n dim"></td></tr>
<tr><td class="dim">every other line</td><td class="n dim" colspan="2">unchanged</td><td class="n dim"></td></tr>
<tr><td>Total debits</td><td class="n">7,777.20</td><td class="n">7,777.20</td><td class="n dim"></td></tr>
<tr><td>Total credits</td><td class="n">7,350.10</td><td class="n">7,777.20</td><td class="n">+427.10</td></tr>
<tr class="total"><td>Imbalance</td><td class="n bad">+427.10</td><td class="n good">0.00</td><td class="n">balanced</td></tr>
<tr><td><strong>Service Charges</strong></td><td class="n bad">not shown</td><td class="n">427.10</td></tr>
<tr><td class="dim">Card Payments</td><td class="n dim">4,975.89</td><td class="n dim">4,975.89</td></tr>
<tr><td>Total money taken</td><td class="n">7,777.20</td><td class="n">7,777.20</td></tr>
<tr><td>Total money earned</td><td class="n">7,350.10</td><td class="n">7,777.20</td></tr>
<tr class="total"><td>Out of balance by</td><td class="n bad">+427.10</td><td class="n good">0.00</td></tr>
</tbody>
</table>
</div>
<h3>The largest repairs of each kind</h3>
<div class="scroll">
<table>
<thead><tr><th>Client</th><th>Date</th><th>Line</th><th class="n">Before</th><th class="n">After</th><th class="n">Day closed</th></tr></thead>
<tbody>
<tr><td class="mono">NGPA</td><td>2026-06-04</td><td>Service Charges</td><td class="n bad">not shown</td><td class="n">1,344.86</td><td class="n good">+1,344.86 → 0</td></tr>
<tr><td class="mono">NTPT</td><td>2026-08-06</td><td>Service Charges</td><td class="n bad">not shown</td><td class="n">427.10</td><td class="n good">+427.10 → 0</td></tr>
<tr><td class="mono">N-30003</td><td>2026-05-27</td><td>Service Charges</td><td class="n bad">not shown</td><td class="n">405.83</td><td class="n good">+405.83 → 0</td></tr>
<tr><td class="mono">NGFL</td><td>2026-05-19</td><td>Tip</td><td class="n">238.46</td><td class="n">70.42</td><td class="n good">168.04 → 0</td></tr>
<tr><td class="mono">NGMI</td><td>2026-07-09</td><td>Tip</td><td class="n">230.01</td><td class="n">80.01</td><td class="n good">150.00 → 0</td></tr>
<tr><td class="mono">NGVA</td><td>2026-07-03</td><td>Tip</td><td class="n">152.66</td><td class="n">40.12</td><td class="n good">112.54 → 0</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>The customer paid $427.10 of service charge as part of a $4,975.89 card tender, so the money
arrived on the debit side. No line credited it, so the day showed $427.10 more collected than
earned. Adding the credit closes it exactly, and the single order responsible is
<span class="mono">square/order/NTPT-PT-KrMZzcon1cpQEJUyetErkBIpcdEZY</span>, carrying a
<code>:sales-order/service-charge</code> of 427.10 on a 3,198.78 order.</p>
</div>
<h3>The largest tip repairs</h3>
<div class="scroll">
<table>
<thead><tr><th>Client</th><th>Date</th><th class="n">Tip before</th><th class="n">Tip after</th><th class="n">Imbalance before</th><th class="n">After</th></tr></thead>
<tbody>
<tr><td class="mono">NGFL</td><td>2026-05-19</td><td class="n">238.46</td><td class="n">70.42</td><td class="n bad">168.04</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGMI</td><td>2026-07-09</td><td class="n">230.01</td><td class="n">80.01</td><td class="n bad">150.00</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGVA</td><td>2026-07-03</td><td class="n">152.66</td><td class="n">40.12</td><td class="n bad">112.54</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGLK</td><td>2026-08-04</td><td class="n">482.94</td><td class="n">422.94</td><td class="n bad">60.00</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGPA</td><td>2026-06-15</td><td class="n">383.70</td><td class="n">331.19</td><td class="n bad">52.51</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGLK</td><td>2026-06-09</td><td class="n">518.88</td><td class="n">467.69</td><td class="n bad">51.19</td><td class="n good">0.00</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>Every one overstated tip income, and in every case the correction equals the imbalance to the
cent. NGFL was crediting $238.46 of tips on a day where $168.04 had been given back.</p>
</div>
<h3>The largest service-charge repairs</h3>
<div class="scroll">
<table>
<thead><tr><th>Client</th><th>Date</th><th class="n">Service charges before</th><th class="n">After</th><th class="n">Imbalance before</th><th class="n">After</th></tr></thead>
<tbody>
<tr><td class="mono">NGPA</td><td>2026-06-04</td><td class="n bad">absent</td><td class="n">1,344.86</td><td class="n bad">+1,344.86</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NTPT</td><td>2026-08-06</td><td class="n bad">absent</td><td class="n">427.10</td><td class="n bad">+427.10</td><td class="n good">0.00</td></tr>
<tr><td class="mono">N-30003</td><td>2026-05-27</td><td class="n bad">absent</td><td class="n">405.83</td><td class="n bad">+405.83</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGPA</td><td>2026-07-02</td><td class="n bad">absent</td><td class="n">348.45</td><td class="n bad">+348.45</td><td class="n good">0.00</td></tr>
<tr><td class="mono">NGA1</td><td>2026-06-04</td><td class="n bad">absent</td><td class="n">296.35</td><td class="n bad">+296.35</td><td class="n good">0.00</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>NGPA 2026-06-04 is the single largest repair in the ninety days: $1,344.86 of service charges
collected from customers and credited to no revenue account at all.</p>
</div>
<h3>Where both fixes land on one day</h3>
<div class="scroll">
<table>
<thead><tr><th>NGNP, 2026-06-25</th><th class="n">Before</th><th class="n">After</th><th class="n">Change</th></tr></thead>
<tbody>
<tr><td>Tip (credit)</td><td class="n">93.90</td><td class="n">92.10</td><td class="n">1.80</td></tr>
<tr><td>Service Charges (credit)</td><td class="n bad">absent</td><td class="n">301.40</td><td class="n">+301.40</td></tr>
<tr class="total"><td>Imbalance</td><td class="n bad">+299.60</td><td class="n good">0.00</td><td class="n">301.40 1.80</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>The two corrections pull in opposite directions and still land on zero, which is a useful
check that they are independent and neither is compensating for the other.</p>
<p>In every case the correction equals the imbalance exactly, which is what you would expect if the fix is recording something real that was recorded nowhere. On NGNP 2026-06-25 both fixes land on one day and pull opposite ways — $301.40 credited, $1.80 removed, $299.60 closed — a useful check that they are independent.</p>
</div>
</section>
<section>
<h2>What is left</h2>
<h2>What was done to the data, and how it was checked</h2>
<div class="measure">
<p>Fifteen client-days above the ten-cent materiality threshold, out of 8,350. Everything else — 90 client-days — totals <strong>$2.26</strong>.</p>
<p>Every step below was performed against a restored copy of the production database. Production itself was never touched.</p>
</div>
<div class="scroll">
<table>
<thead><tr><th>Client</th><th>Date</th><th class="n">Variance</th><th>Note</th></tr></thead>
<tbody>
<tr><td class="mono">NGBK</td><td>2026-08-06</td><td class="n">+299.42</td><td>tender exceeds order totals by exactly this much</td></tr>
<tr><td class="mono">NGMV</td><td>2026-05-24</td><td class="n">+100.87</td><td rowspan="5" class="dim">a five-day cluster in late May, new to this window and not yet diagnosed</td></tr>
<tr><td class="mono">NGMV</td><td>2026-05-20</td><td class="n">+72.05</td></tr>
<tr><td class="mono">NGMV</td><td>2026-05-21</td><td class="n">+57.64</td></tr>
<tr><td class="mono">NGMV</td><td>2026-05-22</td><td class="n">+14.41</td></tr>
<tr><td class="mono">NGMV</td><td>2026-05-26</td><td class="n">+14.41</td></tr>
<tr><td class="mono">NGEB</td><td>2026-05-19</td><td class="n">79.01</td><td rowspan="4" class="dim">ezCater fee semantics — the plan's open question §15.4</td></tr>
<tr><td class="mono">NGEB</td><td>2026-06-23</td><td class="n">50.28</td></tr>
<tr><td class="mono">NGEB</td><td>2026-05-13</td><td class="n">49.80</td></tr>
<tr><td class="mono">NGEB</td><td>2026-07-29</td><td class="n">20.00</td></tr>
<tr><td class="mono">NGDA</td><td>2026-08-01</td><td class="n">50.00</td><td>auto-gratuity booked as a service charge</td></tr>
<tr><td class="mono">N-30012</td><td>2026-05-20</td><td class="n">+17.32</td><td rowspan="2" class="dim">two consecutive days, undiagnosed</td></tr>
<tr><td class="mono">N-30012</td><td>2026-05-21</td><td class="n">+12.99</td></tr>
<tr><td class="mono">NG4S</td><td>2026-05-29</td><td class="n">11.78</td><td class="dim">undiagnosed</td></tr>
<tr><td class="mono">PNSP</td><td>2026-07-12</td><td class="n">0.14</td><td class="dim">register rounding, just over threshold</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>Widening from thirty days to ninety surfaced two clusters the shorter window could not see: <strong>NGMV</strong> across five days in late May, and <strong>NGEB</strong> across four days spanning May to July. NGEB is the known ezCater fee-semantics question. NGMV and N-30012 are new and worth a look before this ships — they are the kind of thing only a longer baseline exposes.</p>
<p>The ten deactivated twins, excluded above, contribute a further $1,142.98 across three days. They should not be reported on at all once one client per location is settled.</p>
</div>
</section>
<section>
<h2>Two performance defects found on the way</h2>
<div class="measure">
<p>Running the job 56,700 times surfaced two problems that a normal nightly pass would hide, both now fixed or documented.</p>
</div>
<div class="scroll">
<table>
<thead><tr><th>Problem</th><th>Effect</th><th>Fix</th></tr></thead>
<tbody>
<tr><td><code>dirty-sales-summaries</code> scanned the whole index</td><td class="n">1,321 ms → 5.6 ms per client</td><td>bound the scan at the client boundary</td></tr>
<tr><td>Transactor sized for a toy database</td><td class="n">~35/min → 5,300/min</td><td>object cache 2 GB → 8 GB, heap 4 GB → 16 GB</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>The second is a deployment setting rather than a code change, but it is dramatic: with a 2 GB object cache against a 27 GB database, the final 7,958 client-days of a pass were crawling at about 35 a minute. After resizing they completed in 90 seconds. Worth checking what production's transactor is sized at.</p>
</div>
</section>
<section>
<h2>Reproduce it</h2>
<div class="scroll">
<pre><span class="dim">;; connect to the restored backup</span>
(def conn (d/connect "datomic:dev://localhost:4337/integreat-prod-restore"))
<span class="dim">;; baseline mode: clear the flag, restore tendered-only tips</span>
(doseq [[c _] clients]
@(d/transact conn [[:db/retract c :client/feature-flags "summary-service-charges"]]))
(alter-var-root #'ss/get-tip (constantly baseline-get-tip))
<span class="dim">;; mark the window and run the real job, then capture</span>
(doseq [[c _] clients] (ss/mark-dirty c w90-start w90-end))
(pmap (fn [[c code]] (ss/refresh-client! c code)) pending)
<span class="dim">;; the single orders behind the worked examples</span>
(d/pull (d/db conn) '[*]
[:sales-order/external-id
"square/order/NGLK-SM-OxSX9gpXJV394qqT8mnBGypUwKNZY"])
(d/pull (d/db conn) '[*]
[:sales-order/external-id
"square/order/NTPT-PT-KrMZzcon1cpQEJUyetErkBIpcdEZY"])</pre>
</div>
<div class="measure">
<p>The unit tests covering both fixes and the re-key:</p>
</div>
<div class="scroll">
<pre>lein test auto-ap.jobs.sales-summaries-test auto-ap.square.core3-test
<span class="dim">;; =&gt; 15 tests, 26 assertions, 0 failures</span></pre>
</div>
<div class="callout warn">
<span class="h">Two defects still open before this ships</span>
<p>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, <code>remove-voided-orders</code> retracts orders, and 63% of the contended clients' charges have two parents, so a retraction there deletes the other client's payment.</p>
</div>
</section>
<section>
<h2>Isolation: which entities could still swap owners</h2>
<div class="measure">
<p>Balancing was only half the problem. The other half is whether an entity can change hands
between two clients at all. Rather than reason about it, every entity type the importers
create with a unique external id was audited, and ownership was read out of the database's
own history.</p>
</div>
<h3>Entities whose client has actually changed</h3>
<div class="scroll">
<table>
<thead><tr><th>Entity</th><th class="n">Swapped</th><th class="n">Of total</th><th>Key before</th></tr></thead>
<tbody>
<tr><td>Expected deposits (Square payouts)</td><td class="n bad">4,069</td><td class="n">144,688</td><td class="mono">square/payout/&lt;id&gt;</td></tr>
<tr><td>Cash drawer shifts</td><td class="n bad">2,628</td><td class="n">69,291</td><td class="mono">square/cash-drawer-shift/&lt;id&gt;</td></tr>
<tr><td>Refunds</td><td class="n">3,387</td><td class="n">51,990</td><td class="dim">fixed earlier</td></tr>
<tr><td>Sales orders, ezCater orders</td><td class="n good">0</td><td class="n dim"></td><td class="dim">already scoped</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>Both payout and cash-shift endpoints are filtered by location, so two clients configured on
one Square location import the same records and collide on a single entity — the identical
defect refunds and charges had, in two types nobody had looked at.</p>
</div>
<div class="callout warn">
<span class="h">Nineteen client pairs have contended, but only ten are visible today</span>
<p>Nine pairs no longer share a location in the current configuration, so no point-in-time
check would find them — yet their data is still mixed. The largest are NGMJ/NGSC with 1,546
affected entities, NGAK/NGMH with 952, NGBW/NGWD with 780 and NGNP/NGVZ with 645. This is the
argument for scoping the keys rather than only deactivating what currently looks shared:
deactivation is a snapshot fix that rots the next time someone configures a location twice.</p>
<p>Worth knowing alongside it: <strong>105 of the clients share a single Square auth token</strong>,
so these importers all reach into one Square account. The blast radius of a future
misconfiguration is the estate, not a pair.</p>
</div>
<h3>What was done, and what it cost the books</h3>
<div class="scroll">
<table>
<thead><tr><th>Step</th><th>Result</th></tr></thead>
<tbody>
<tr><td>Re-key expected deposits</td><td class="n">144,652 re-keyed · 0 collisions · 36 unscopable</td></tr>
<tr><td>Re-key cash drawer shifts</td><td class="n">69,291 re-keyed · 0 collisions · 0 unscopable</td></tr>
<tr><td><strong>Entity counts before vs after</strong></td><td class="n good">unchanged on all four types</td></tr>
<tr><td>Legacy-scheme keys remaining</td><td class="n good">0</td></tr>
<tr><td>Live re-import, 66 of 102 clients, 90-day window</td><td class="n good">0 entities created · 0 values rewritten</td></tr>
<tr><td><strong>Ownership changes since the re-key</strong></td><td class="n good">0 deposits · 0 shifts · 0 refunds</td></tr>
<tr><td><strong>Summaries recomputed and compared</strong></td><td class="n good">18,900 · 0 differences</td></tr>
<tr><td>Deactivate the duplicate client at each shared location</td><td class="n">10 locations · shared locations remaining: <span class="good">0</span></td></tr>
<tr><td>Give every order its own payment record</td><td class="n">189,167 re-keyed · 77,599 copied</td></tr>
<tr><td><strong>Payments owned by two orders</strong></td><td class="n good">11,469 → 0</td></tr>
<tr><td>Client-scope refunds, payouts and cash-drawer shifts</td><td class="n good">counts unchanged · 0 collisions</td></tr>
<tr><td>Live Square import afterwards</td><td class="n good">0 orders with duplicated payment · 0 shared payments</td></tr>
<tr><td>Ownership changes after the change</td><td class="n good">0 refunds · 0 payouts · 0 shifts</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p>The re-import is the important negative result. Every payout and shift the importer fetched
resolved to the client's own entity and wrote nothing — the re-key is transparent to the
importer, which is exactly what the legacy-key fallback is for. And recomputing all 18,900
client-days afterwards reproduced the previous figures exactly — largest difference 0.000000, so re-keying 213,943
entities moved no money at all.</p>
<p>One caveat worth stating plainly: re-keying stops future contention, but it does not
retrospectively re-attribute records that were claimed by the wrong client while the
configuration was shared. Those stay where they were last written. Correcting them is a
separate exercise, and one to do only once the business decides which client owns each
location.</p>
<p>The count checks are the ones that matter. If re-keying had gone wrong it would have created a second copy of every record rather than updating the existing one, and the totals would have doubled. They did not move. The payment-copy step is the exception and is meant to add records — it added exactly 77,599, matching the number of copies it reported making.</p>
</div>
<div class="callout warn">
<span class="h">A bug in this work, found by measuring rather than reading</span>
<p>The first attempt at copying shared payments derived each payment's Square identifier by stripping a fixed prefix. That is right the first time a payment is seen, but once it has been re-keyed to one client, a second order meeting it later read the already-scoped key as the identifier and scoped it twice — <span class="mono">NGCD-CD-NGCC-CC-&lt;id&gt;</span>. The importer then created a fresh payment, doubling the tender on five clients by $3,000$7,000 each. It was caught because the totals were absurd, not because the code looked wrong. The fix recovers the scope from the record itself; client codes contain dashes, so it cannot be done by pattern. A test now runs the step one order at a time, which is the arrangement that exposes it.</p>
</div>
</section>
<section>
<h2>Why <code>remove-voided-orders</code> is dangerous</h2>
<h2>What is still out of balance</h2>
<div class="measure">
<p>The schema says a charge belongs to its order:</p>
<p>Fifteen client-days across ninety days and 8,350 client-days, totalling $850.12. Everything else — 90 further days — comes to <strong>$2.26</strong>, the largest being 9.00¢, which is till rounding.</p>
</div>
<div class="scroll">
<pre>{:db/ident :sales-order/charges, :db/isComponent true}</pre>
<table>
<thead><tr><th>Client</th><th>Date</th><th class="n">Variance</th><th>What it is</th></tr></thead>
<tbody>
<tr><td class="mono">NGBK</td><td>2026-08-06</td><td class="n">+299.42</td><td>Square recorded more payment than the orders account for</td></tr>
<tr><td class="mono">NGMV</td><td>May 2026</td><td class="n">+259.38</td><td>five days, undiagnosed</td></tr>
<tr><td class="mono">NGEB</td><td>May 13 Jul 29</td><td class="n">199.09</td><td>four days, ezCater fee treatment — an open question</td></tr>
<tr><td class="mono">NGDA</td><td>2026-08-01</td><td class="n">50.00</td><td>auto-gratuity recorded as a service charge</td></tr>
<tr><td class="mono">N-30012</td><td>May 2021</td><td class="n">+30.31</td><td>two days, undiagnosed</td></tr>
<tr><td class="mono">NG4S</td><td>2026-05-29</td><td class="n">11.78</td><td>undiagnosed</td></tr>
<tr><td class="mono">PNSP</td><td>2026-07-12</td><td class="n">0.14</td><td>till rounding, just over the threshold</td></tr>
</tbody>
</table>
</div>
<div class="measure">
<p><code>:db/isComponent</code> means Datomic treats charges as <em>parts of</em> the order rather
than independent records. So <code>[:db/retractEntity &lt;order&gt;]</code> deletes the order
<strong>and its charges</strong>. That is the documented behaviour, and normally it is what you want.</p>
<p><code>remove-voided-orders</code> asks Square for the last ten days of orders, keeps the ones
Square reports as voided, and retracts them. Correct on its own terms.</p>
<p>The problem is that where two clients shared a location, both clients' orders resolved to the
<strong>same charge entity</strong>, because charge keys carried no client:</p>
<p>The ten deactivated duplicate records contribute a further $1,142.98 across three days. They are excluded above and should be excluded from reporting generally, since they are now dormant copies.</p>
<p>Two clusters — NGMV and N-30012, both in late May — are new and unexplained. They only became visible because the window was widened to ninety days; a thirty-day view did not reach them. They are worth a look before this ships.</p>
</div>
</section>
<section>
<h2>Decisions and risks still open</h2>
<div class="scroll">
<pre>NGCD order ──┐
├──&gt; charge 17592490524 ← one entity, two parents
NGCC order ──┘</pre>
<table>
<thead><tr><th>Item</th><th>Who decides</th><th>Why it matters</th></tr></thead>
<tbody>
<tr><td>Which client record survives at each shared location</td><td>the business</td><td>the newer record generally has no history before the split, so keeping it loses years of the location's books</td></tr>
<tr><td>Which revenue account service charges post to</td><td>accounting</td><td>currently 49000 Service Income, chosen so the work could be measured; it affects reporting, never whether a day balances</td></tr>
<tr><td>Whether to correct records the wrong client already owns</td><td>the business</td><td>the fix stops future mix-ups; it does not retrospectively move records claimed while the configuration was shared</td></tr>
<tr><td><code>remove-voided-orders</code></td><td>engineering</td><td>safe once no payment has two parent orders; worth guarding regardless so it detaches rather than deletes</td></tr>
</tbody>
</table>
</div>
<div class="callout warn">
<span class="h">Two operational findings, unrelated to the summaries</span>
<p><strong>The production backup had not written a restore point since 2025-03-10</strong> — roughly seventeen months — even though data files were still uploading daily. A backup you cannot restore from is not a backup. A fresh one was taken on 2026-08-14 and is what this work used.</p>
<p><strong>The database server was sized for a toy dataset</strong>: a 2 GB cache against 27 GB of data. A recompute was crawling at about 35 client-days a minute; after raising the cache to 8 GB the remaining 7,958 finished in 90 seconds. Worth checking what production is set to.</p>
</div>
</section>
<section>
<h2>How to check any of this <span class="tech">technical</span></h2>
<div class="scroll">
<pre><span class="dim">;; the restored database, untouched production as of 2026-08-14 22:52</span>
(def conn (d/connect "datomic:dev://localhost:4337/integreat-prod-restore"))
<span class="dim">;; the two orders behind the worked examples</span>
(d/pull (d/db conn) '[*] [:sales-order/external-id
"square/order/NGLK-SM-OxSX9gpXJV394qqT8mnBGypUwKNZY"])
(d/pull (d/db conn) '[*] [:sales-order/external-id
"square/order/NTPT-PT-KrMZzcon1cpQEJUyetErkBIpcdEZY"])
<span class="dim">;; the gate: no payment may have two parent orders</span>
(rk/charges-with-multiple-parents (d/db conn) orders) <span class="dim">;; =&gt; 0</span>
<span class="dim">;; ownership history — which records ever changed client</span>
(->> (d/datoms (d/history (d/db conn)) :aevt :sales-refund/client)
(filter :added)
(reduce (fn [m d] (update m (:e d) (fnil conj #{}) (:v d))) {})
(filter (fn [[_ owners]] (&gt; (count owners) 1)))
count)</pre>
</div>
<div class="measure">
<p>Retract either order and Datomic deletes that shared charge. The other client's order survives
with its sales intact but its payment gone: the day silently goes out of balance and the tender
disappears from the current database value. Measured on the restore,
<strong>35,870 of 56,829 charges (63%)</strong> in the contended clients' recent window have two
parent orders.</p>
<p>The re-key stops new sharing but does not un-share those. Two remedies, either sufficient:
split them by re-importing the affected window now that keys are client-scoped, or guard the
retraction so it detaches a shared charge instead of deleting it. The guard is small and makes
the operation safe whatever shape the data is in.</p>
<p>The comparison tool is committed as <code>auto-ap.jobs.compare-sales-summaries</code>, which diffs summaries between two points in the same database using <code>as-of</code> — so "before" is production's own stored figures rather than a re-simulation. Unit tests: <code>lein test auto-ap.jobs.sales-summaries-test auto-ap.square.core3-test auto-ap.jobs.rekey-square-external-ids-test</code>.</p>
</div>
</section>
<footer>
<span>Measured 2026-08-15 on <span class="mono">integreat-prod-restore</span>, restored from backup point 209608347. Nothing in production was touched. Branch <span class="mono">worktree-sales-summary-balance</span>.</span>
<span>Service charges map to account 49000 Service Income, chosen to make measurement possible and still pending accounting sign-off. The account affects only whether a day can reach accepted — never whether it balances — so no figure here depends on it.</span>
<span>Measured 2026-08-15 against <span class="mono">integreat-prod-restore</span>, restored from backup point 209608347 — production as of 2026-08-14 22:52. Nothing in production was read or written. Branch <span class="mono">worktree-sales-summary-balance</span>.</span>
<span>A day counts as out of balance when money taken minus money earned is half a penny or more. "Material" means ten cents or more, the threshold below which the residual is till rounding.</span>
</footer>
</div>