compare-sales-summaries diffs summaries between two points in the same database using as-of, so a recompute can be audited against exactly what was there before without a snapshot or scratch copy. It answers a question that balance-status counting hides: a day can stay balanced while its line amounts move, which is a real change to the books even though no red turns green. Amounts are compared at the cent so floating-point noise does not read as a change. Run over 2026-07-15 to 08-13 against production's own summaries at basis-t 209608347: out of balance -> balanced 309 balanced -> out of balance 0 balanced -> balanced, numbers moved 237 no summary in production 4,058 Broken down by fix, neither calculation change touches a healthy day: R1 fixes 86 with 0 unbalanced and 0 balanced-day edits, R2 fixes 276 on the same terms. Every one of the 237 balanced-but-changed days comes from the data work, which is also the only step that unbalances anything — 19 days, all of them absorbed by R1 and R2, which is why the end-to-end count is zero. The 4,058 missing summaries include eight consecutive days, Jul 30 to Aug 6, missing for all 210 clients — the coverage hole the plan predicted from the scheduler's seven-day lookback, confirmed here independently from the data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
421 lines
20 KiB
HTML
421 lines
20 KiB
HTML
<title>Thirty-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;
|
||
--shadow: 0 1px 2px rgba(20,31,29,.06), 0 8px 24px rgba(20,31,29,.05);
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--paper: #0E1615;
|
||
--card: #151F1E;
|
||
--ink: #E8EFED;
|
||
--ink-soft: #A3B3AF;
|
||
--ink-faint: #74847F;
|
||
--rule: #26332F;
|
||
--accent: #5FBDB4;
|
||
--accent-soft: #16302E;
|
||
--good: #5FBE8C;
|
||
--bad: #E08A72;
|
||
--warn: #D6AC55;
|
||
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.3);
|
||
}
|
||
}
|
||
:root[data-theme="dark"] {
|
||
--paper: #0E1615;
|
||
--card: #151F1E;
|
||
--ink: #E8EFED;
|
||
--ink-soft: #A3B3AF;
|
||
--ink-faint: #74847F;
|
||
--rule: #26332F;
|
||
--accent: #5FBDB4;
|
||
--accent-soft: #16302E;
|
||
--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);
|
||
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
||
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"; }
|
||
.mono {
|
||
font-family: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", 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; }
|
||
.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;
|
||
}
|
||
p { margin: 0; }
|
||
.measure p + p { margin-top: 14px; }
|
||
|
||
/* headline before/after */
|
||
.ledger {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr;
|
||
align-items: stretch;
|
||
gap: 0;
|
||
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;
|
||
}
|
||
.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); }
|
||
|
||
/* stat row */
|
||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 14px; }
|
||
.stat {
|
||
background: var(--card); border: 1px solid var(--rule); border-radius: 4px;
|
||
padding: 18px 20px; display: flex; flex-direction: column; gap: 4px;
|
||
}
|
||
.stat .k { font-size: 30px; font-weight: 650; letter-spacing: -.02em; line-height: 1; }
|
||
.stat .l { font-size: 13px; color: var(--ink-soft); }
|
||
.stat.zero .k { color: var(--good); }
|
||
.stat.flag .k { color: var(--warn); }
|
||
|
||
/* steps */
|
||
.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; }
|
||
|
||
.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; }
|
||
th, td { padding: 11px 16px; text-align: left; border-bottom: 1px solid var(--rule); white-space: nowrap; }
|
||
thead th {
|
||
font-size: 11.5px; letter-spacing: .09em; text-transform: uppercase;
|
||
color: var(--ink-faint); font-weight: 700; background: var(--accent-soft);
|
||
}
|
||
tbody tr:last-child td { border-bottom: none; }
|
||
td.n, th.n { text-align: right; font-variant-numeric: tabular-nums; }
|
||
tr.total td { font-weight: 650; background: var(--accent-soft); }
|
||
.good { color: var(--good); font-weight: 650; }
|
||
.bad { color: var(--bad); font-weight: 650; }
|
||
.dim { color: var(--ink-faint); }
|
||
|
||
.pill {
|
||
display: inline-block; font-size: 11.5px; font-weight: 700;
|
||
letter-spacing: .05em; text-transform: uppercase;
|
||
padding: 3px 9px; border-radius: 3px;
|
||
}
|
||
.pill.ok { background: var(--accent-soft); color: var(--good); }
|
||
.pill.no { background: var(--accent-soft); color: var(--bad); }
|
||
|
||
.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;
|
||
}
|
||
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; }
|
||
</style>
|
||
|
||
<div class="wrap">
|
||
|
||
<header class="masthead">
|
||
<div class="eyebrow">Sales summaries · restored production backup</div>
|
||
<h1>Thirty-Day Reconciliation</h1>
|
||
<p class="standfirst">What the deduplication work and the two calculation fixes actually did to the books, measured day by day against production's own summaries.</p>
|
||
<div class="meta">
|
||
<span><b>Window</b> 2026-07-15 → 2026-08-13</span>
|
||
<span><b>Client-days</b> <span class="num">3,040</span> with activity</span>
|
||
<span><b>Compared against</b> basis-t <span class="num">209608347</span></span>
|
||
</div>
|
||
</header>
|
||
|
||
<section>
|
||
<div class="ledger">
|
||
<div>
|
||
<span class="side-label">Baseline</span>
|
||
<span class="figure num">$22,527.40</span>
|
||
<span class="subfig"><span class="num">398</span> days out of balance · <span class="num">86.00%</span> clean</span>
|
||
</div>
|
||
<div class="arrow" aria-hidden="true">→</div>
|
||
<div>
|
||
<span class="side-label">After fixes</span>
|
||
<span class="figure after num">$405.66</span>
|
||
<span class="subfig"><span class="num">67</span> days out of balance · <span class="num">97.64%</span> clean</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div class="stat"><span class="k num">331</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 flag"><span class="k num">237</span><span class="l">already-balanced days whose numbers moved</span></div>
|
||
<div class="stat"><span class="k num">98.2%</span><span class="l">of the dollar variance removed</span></div>
|
||
</div>
|
||
|
||
<div class="measure">
|
||
<p>Both arms are computed on the same deduplicated data, so this isolates what the <em>calculation</em> 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).</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Was anything already balanced disturbed?</h2>
|
||
<div class="measure">
|
||
<p>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 <strong>237 client-days that were balanced had their line amounts change anyway</strong>, and a balance-only view would hide every one of them.</p>
|
||
<p>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.</p>
|
||
</div>
|
||
|
||
<div class="scroll">
|
||
<table>
|
||
<thead>
|
||
<tr><th>Outcome, production → final</th><th class="n">Client-days</th><th>Reading</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>Out of balance → balanced</td><td class="n good">309</td><td class="dim">the intended repair</td></tr>
|
||
<tr><td>Balanced → out of balance</td><td class="n good">0</td><td class="dim">nothing was broken</td></tr>
|
||
<tr><td>Balanced → balanced, numbers changed</td><td class="n" style="color:var(--warn);font-weight:650">237</td><td class="dim">amounts moved, balance held</td></tr>
|
||
<tr><td>Balanced → balanced, identical</td><td class="n">1,652</td><td class="dim">untouched</td></tr>
|
||
<tr><td>Still out of balance</td><td class="n">44</td><td class="dim">residual, see below</td></tr>
|
||
<tr class="total"><td>Production summaries in window</td><td class="n">2,242</td><td class="dim">of 6,300 client-day slots</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<h3>What moved on those 237 days</h3>
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>Pattern</th><th class="n">Days</th><th>Cause</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Every category 0.00 → real values</td><td class="n">~100</td><td>production held an all-zero summary for a day that had orders</td></tr>
|
||
<tr><td>Card Payments ↔ Fees reallocation</td><td class="n">~135</td><td>the payout arrived, so the processing fee is now known and booked separately</td></tr>
|
||
<tr><td>Service Charges line appears</td><td class="n">13</td><td>R2, on days that were already out of balance</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="measure">
|
||
<p>The second pattern is worth reading carefully: <code>Card Payments</code> falls by exactly what <code>Fees</code> 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.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Impact by underlying fix</h2>
|
||
<div class="measure">
|
||
<p>Applied in sequence, each measured against the state before it. Percentages of a 3,040 client-day population.</p>
|
||
</div>
|
||
|
||
<div class="step" data-step="1">
|
||
<h3>Data</h3>
|
||
<div class="step-title">Deduplication — Phase 0, re-key, re-import</div>
|
||
</div>
|
||
<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">1,581</td></tr>
|
||
<tr><td>Numbers changed</td><td class="n">547</td></tr>
|
||
<tr><td>Out of balance → balanced</td><td class="n good">56</td></tr>
|
||
<tr><td>Balanced → out of balance</td><td class="n bad">19</td></tr>
|
||
<tr><td>Balanced → balanced, numbers changed</td><td class="n" style="color:var(--warn);font-weight:650">237</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="measure">
|
||
<p>The data work is the only step that unbalances anything: <strong>19 days</strong> 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.</p>
|
||
</div>
|
||
|
||
<div class="step" data-step="2">
|
||
<h3>R1</h3>
|
||
<div class="step-title">Tips on untendered orders</div>
|
||
</div>
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>Outcome</th><th class="n">Client-days</th><th class="n">Share</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Unchanged</td><td class="n">2,941</td><td class="n dim">96.7%</td></tr>
|
||
<tr><td>Out of balance → balanced</td><td class="n good">86</td><td class="n dim">2.8%</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">13</td><td class="n dim">0.4%</td></tr>
|
||
<tr class="total"><td>Days touched · dollars moved</td><td class="n">99</td><td class="n">$973.79</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="step" data-step="3">
|
||
<h3>R2</h3>
|
||
<div class="step-title">Square service charges, both signs</div>
|
||
</div>
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>Outcome</th><th class="n">Client-days</th><th class="n">Share</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Unchanged</td><td class="n">2,760</td><td class="n dim">90.8%</td></tr>
|
||
<tr><td>Out of balance → balanced</td><td class="n good">276</td><td class="n dim">9.1%</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">4</td><td class="n dim">0.1%</td></tr>
|
||
<tr class="total"><td>Days touched · dollars moved</td><td class="n">280</td><td class="n">$23,729.45</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<span class="h">Both calculation fixes are inert on healthy days</span>
|
||
<p>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.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Days production never had</h2>
|
||
<div class="measure">
|
||
<p>Of 6,300 client-day slots in the window, production held a summary for only 2,242. The remaining <strong>4,058</strong> had none at all — nothing to compare against, and nothing an accountant could have looked at.</p>
|
||
<p>They are not evenly spread. Eight consecutive days show <span class="num">210</span> missing summaries each, which is <em>every client in the database</em>:</p>
|
||
</div>
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>Dates</th><th class="n">Missing per day</th><th>Reading</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Jul 15 – Jul 29</td><td class="n">117</td><td class="dim">clients with no summary that day</td></tr>
|
||
<tr><td class="bad">Jul 30 – Aug 6</td><td class="n bad">210</td><td class="bad">every client — a total coverage hole</td></tr>
|
||
<tr><td>Aug 7 – Aug 13</td><td class="n">89</td><td class="dim">clients with no summary that day</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="callout warn">
|
||
<span class="h">The coverage hole is real and self-confirming</span>
|
||
<p>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.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>What is left</h2>
|
||
<div class="measure">
|
||
<p>Five client-days above the ten-cent materiality threshold, totalling $404.04. Everything else — 62 days — comes to <strong>$1.62</strong>, with the largest single day at nine cents.</p>
|
||
</div>
|
||
<div class="scroll">
|
||
<table>
|
||
<thead><tr><th>Client</th><th>Date</th><th class="n">Variance</th><th>Explanation</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="mono">NGBK</td><td>2026-08-06</td><td class="n">+$299.42</td><td>refunds now held by both twins — a re-key consequence</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">NGEB</td><td>2026-08-10</td><td class="n">−$25.00</td><td>ezCater fee semantics <span class="pill ok">predicted</span></td></tr>
|
||
<tr><td class="mono">NGEB</td><td>2026-07-29</td><td class="n">−$20.00</td><td>ezCater fee semantics <span class="pill ok">predicted</span></td></tr>
|
||
<tr><td class="mono">NGPS</td><td>2026-08-12</td><td class="n">+$9.62</td><td>unexplained <span class="pill ok">predicted</span></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="measure">
|
||
<p>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.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>How this was measured</h2>
|
||
<div class="measure">
|
||
<ul>
|
||
<li>Run against a production backup restored to basis-t <span class="num">209608347</span>, newest transaction 2026-08-14 22:52. Nothing in production was touched.</li>
|
||
<li>"Before" is production's own stored summaries, read via <code>as-of</code> — not a re-simulation of them. Datomic keeps every past value, so the comparison is against exactly what was there.</li>
|
||
<li>A day counts as out of balance when debits minus credits is at least half a cent. Line amounts are compared at the cent, so floating-point noise does not read as a change.</li>
|
||
<li>R1 and R2 only ever add credits, so baseline is derived as <code>fixed + untendered tip + service charges</code>. That identity was checked against a from-scratch baseline recomputation on 20 random client-days and agreed on every one.</li>
|
||
<li>All 14,458 summaries in the database were recomputed through the real job, not a test harness.</li>
|
||
</ul>
|
||
</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>
|
||
|
||
<footer>
|
||
<span>Measured 2026-08-15 on <span class="mono">integreat-prod-restore</span>. 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>
|
||
</footer>
|
||
|
||
</div>
|