How it works
Five clinics, seven source systems that disagree with each other, and one number at the end of it that has to be right. Every step in between, in the order it happens — what arrives, how it is read, how a payment is turned into revenue, what is checked, what happens when a check fails, and who checks the checker.
1 · Seven systems that were never meant to agree
row counts are liveTwo clinics were acquired and kept their booking software. Payments arrive from two processors on different settlement clocks. Cancellations live in a spreadsheet a manager maintains by hand. None of these were designed to be added together, and none of them are wrong from where they sit.
2 · Four traps that look like typos and are not
| One instant, written three ways | The older booking system writes 2026-03-04 09:00:00 with no timezone at all, meaning clinic-local. The acquired one writes 04/03/2026 day-first with the time in a separate column. The processors settle in UTC. A visit at 5pm in Irvine belongs to that day and not the next one, and only one of these three tells you so. |
| Two money columns, two different units | Every amount in the corpus is decimal dollars except one: the Stripe feed reports integer cents. Reading it the same way as the others inflates that feed a hundredfold, and the total still looks like a plausible number. |
| A blank that means zero, and a blank that means unknown | In the cancellation sheet an empty fee cell means no fee was charged. In the expense sheet an empty amount means nobody filled it in. Treating them alike either invents money or loses it. |
| A total typed by a human, above rows that disagree with it | Each cancellation sheet carries a typed monthly total. It is checked against the rows beneath it, and when they differ both are kept — the typed figure is evidence, not truth. |
3 · The machine, end to end
every fifteen minutes, unattendedSix stages, in order, on a systemd timer. Everything after this section is one of them explained. A hard control failing stops the run before export, so the console keeps the last figures that passed rather than showing new ones that did not.
4 · Sweep and ingest — getting a file in without trusting it
385 live of 16,123 ingested| Hash first, parse later | Every file is SHA-256’d before it is opened. A byte-identical file that has already been ingested is skipped, which is what makes running every fifteen minutes over a folder of mostly-unchanged files cheap and idempotent. On a quiet tick all 385 files are skipped and nothing is loaded twice. |
| The header is a fingerprint | The column list is hashed and checked against the layouts approved for that feed — 8 of them so far. An unrecognised layout does not get parsed on a guess; it fails the file and hands the diagnostician the header alongside every layout that has worked before. That comparison is what makes an added or renamed column a solvable problem rather than a mystery. |
| A month is restated, not appended | Source systems re-export the whole current month, so the same file name arrives again with more rows in it. The previous version is marked superseded rather than deleted, and only live versions count — hence 16,123 ingestions standing behind 385 live files. Appending instead would double a month every quarter of an hour. |
| A refused row is kept | A row the parser cannot read is written to a reject table with the reason and its source line, never dropped. 0 rows are being held now. A hard control asserts that every row of every live file is accounted for, so rejects cannot quietly become a shortfall in a total. |
5 · Model — seven layers, rebuilt on every run
16 SQL models, in dependency orderStaging is written by ingest; everything below it is built by a numbered SQL file that declares the one table it produces, run top to bottom. Nothing is updated in place — each run rebuilds every model from staging, so the warehouse is a pure function of the files that have arrived. There is no accumulated state to drift, and re-running yesterday reproduces yesterday exactly. Each layer may only read the ones above it.
| Staging | stg_appointment · stg_payment · stg_expense · stg_vendor_payment · stg_cancellation_entry · stg_cancellation_total | Written by ingest, not by a model. One row per source row, typed and dated, with the file and line it came from still attached. No decisions have been made yet. |
| Entities | dim_client · dim_vendor | One person and one supplier however many systems they appear in. Everything downstream joins through these. |
| Facts | fct_appointment · fct_payment_base · fct_payment · fct_package · fct_spend | What happened, on one grain, with the source vocabularies collapsed to one. A payment's kind and the evidence for it are decided here. |
| Matches | map_payment_appointment · redemption_candidate · map_redemption · membership_covered_visit · map_reversal | The joins the sources refuse to make: which visit a charge paid for, which session drew down which package, which refund reverses which sale. |
| Ledger | fct_recognition · fct_deferred | What was earned, in which month, and what is still owed as treatments. The identity on this page is a statement about these two. |
| Disagreements | recon_exception | Every place two sources say different things, kept in one of three states rather than resolved by preference. |
| Marts | mart_location_month | The one table the console's money figures are read from. Clinic by month, and nothing aggregated further than that. |
6 · Who is this person, and who is this supplier
a crosswalk, never a fuzzy scoreThe acquired clinics kept their own patient numbering, so one person is PT-IRV-00123 in the booking export and LUM-IRV-C00123 in Stripe. Those are the same string with a different prefix — a documented fact about the migration, not a similarity that happens to score highly. Nothing here is matched on edit distance: a reference fitting neither shape is left unresolved and surfaced, because a payment attached to the wrong person’s chart is worse than one attached to nobody.
| Entity | How it was resolved | Count |
|---|---|---|
| Client | native | 3,538 |
| Client | crosswalk | 2,170 |
| Supplier | resolved | 30 |
| Supplier | unresolved | 1 |
Supplier names are typed by an AP clerk, so they arrive as A Supplier Inc., A Supplier, Inc and a supplier inc. Punctuation and legal suffixes are stripped and the remaining tokens compared as a set — still a rule, still inspectable. The one that fits nothing is on the exception register with its money intact rather than folded into whichever supplier looked closest.
7 · What did this payment earn, and when
cash arriving is not revenue earnedThat is the shape of the decision. This is how it actually landed across every payment in the warehouse — each one records why it was classified the way it was, so a figure can be traced back to the evidence that produced it rather than to a rule someone believes ran.
| Evidence used | What that means | Payments |
|---|---|---|
| matched_client_date_amount | Same person, same day, same amount as a completed visit. The strongest inference available. | 35,903 |
| declared | The processor said what it was — a Stripe membership charge names itself. | 9,128 |
| cancellation_by_client_date | No visit happened, but the cancellation log records a late cancellation for that client that day, and a fee is owed. | 3,904 |
| matched_date_amount | Amount and date match a visit, but the payment carries no customer reference — one in eight Square rows does not. | 3,498 |
| package_price_band | Matches no single visit and prices like a package. Deferred in full and released session by session, never booked as revenue today. | 2,177 |
| matched_date_amount_ambiguous | Two or more visits explain the charge equally well. Recorded as reconciled against the set, and flagged rather than assigned to one. | 1,350 |
| cancellation_by_log_amount | The payment carries no customer reference at all, but the cancellation sheet for that clinic and month records a fee of exactly that amount. Weaker than matching a named client, and recorded as such. | 534 |
| none | Nothing explains it. Quarantined — counted as cash, kept out of revenue, and shown on the register with its amount intact. | 7 |
The ladder is ordered by how much the evidence proves. A processor that declares what it sold beats a match on client, date and amount; that beats a match on date and amount alone; and a price that merely falls in a package band is the weakest rung of all. Anything reaching the bottom is contested or quarantined, never assigned to the most likely answer.
8 · The identity everything else has to satisfy
proved on every run, to the centA medspa takes money long before it does the work. Someone buys six laser sessions in March and turns up for the last one in October, and the business owes them treatments in the meantime. Right now that is $2,674,656 held on the balance sheet: 2,024 sessions on packages the pipeline could schedule, plus $1,681,510 against packages it could not — a guest-checkout deposit with no customer reference buys something, but nothing in the files says what. Getting this wrong does not produce a small error; it produces a business that thinks it is profitable in March and cannot explain October.
9 · Check — 34 controls, in 9 families
a hard failure stops the run; a soft one is reportedThe split is the whole design. A hard control asserts something that cannot be true of correct data — a duplicate primary key, a null in a required column, an identity that does not balance — and its failure stops the run before anything is published. A soft control reports a suspicion: a feed a little late, a contested share a little high. Suspicions are never allowed to stop a run, and never allowed to trigger an automatic repair, because a system that lets a machine act on a hunch will eventually act on the wrong one.
| Family | What it asserts | Hard | Soft |
|---|---|---|---|
| freshness | A feed that stopped arriving, judged against a per-feed SLA on the business clock. | 4 | 3 |
| ledger | The arithmetic identities the books must satisfy, including cash − revenue = deferred. | 5 | 1 |
| nulls | Required columns arriving empty. | 5 | — |
| duplicate_keys | Identifiers that must be unique, and are not. | 4 | — |
| control_total | Typed totals against the rows beneath them, warehouse counts against the ingest log, and every charge inside the price list. | 3 | — |
| referential | Location, service and GL codes the reference data has never heard of. | 3 | — |
| volume | Row count against the trailing median, with a robust band — one spike must not widen it. | — | 3 |
| recon | Whether the amount of disagreement is itself within tolerance. | — | 2 |
| rejects | Rows a parser refused. Every row of every live file must be accounted for. | 1 | — |
10 · When something breaks at three in the morning
85 attempts so far · 2 committedThe shape above is the argument. Below it is the evidence: two of these that actually ran, read out of the run record — what the diagnostician was handed, what it chose, and what the gate did about it. Nothing here is illustrative.
A repair it refused to make
refusedA processor started reporting a different unit. Every figure in the file is internally consistent, so nothing looks corrupt — it is simply a hundred times too large. No action in the set can rescale a number, and the diagnostician said so instead of reaching for the one that would have hidden it.
1What it was shownthe whole evidence base — there is no other input
# Blocked run 20260825T101801188Z ## Checks that blocked the run ### freshness.booking_legacy [hard] expected: within 26h of 2027-11-08 observed: newest row is 2027-11-06 — 48h old note: the feed may have stopped arriving ### freshness.booking_acquired [hard] expected: within 26h of 2027-11-08 observed: newest row is 2027-11-06 — 48h old note: the feed may have stopped arriving ## Service codes finance recognises TOX-STD (Neurotoxin — Standard (20u)), TOX-LRG (Neurotoxin — Full Face (40u)), FILL-1S (Dermal Filler — 1 Syringe), FILL-2S (Dermal Filler — 2 Syringe), LASER-IPL (IPL Photofacial), LASER-RES (Fractional Resurfacing), MICRO-RF (Microneedling RF), HYDRA (HydraFacial), PEEL-MED (Medical-Grade Peel), BODY-CYC (Body Contouring Cycle), WEIGHT-INJ (Weight Management Injection), CONSULT (New Patient Consultation)
continues — scroll inside the box
2What it choseone action of five · 10.7s
{
"action": "escalate"
}“Both hard freshness checks show booking_legacy and booking_acquired are 48h stale with no new rows, indicating the feeds stopped arriving upstream — no available action repairs missing/late data, and quarantine_file would wrongly exclude real, merely-delayed transactions.”
3Why nothing was appliedno repair reached the gate
the diagnostician declined to propose a repair
The model may propose exactly five things
| approve schema | a source added a column and the new shape is safe to accept |
| remap column | a column was renamed; point the parser at the new name |
| map treatment | an unrecognised service code, matched to one in the catalogue |
| quarantine file | hold a file out of the marts rather than load it wrong |
| escalate | nothing above fits — stop and tell a person |
There is no “run this SQL” action and no escape hatch. Anything the set cannot express is escalated by construction rather than by judgement.
Where the boundary sits
| Deterministic code | The one model call |
|---|---|
| Parsing, matching, recognition, every figure | Reads a brief about a failure and names one action |
| All 34 controls, and the proof gate itself | Cannot see the repository, the shell, or the internet |
| What gets published, and what does not | Cannot commit anything; the gate decides that |
No figure on this site was produced by a model, and this console makes no model call at all. The diagnostician is a mechanic called out to a specific fault, not an author of the accounts.
11 · What the diagnostician actually is
85 calls in 2,088 runs| The call | claude -p <brief> --model sonnet --tools "" --strict-mcp-config — the Claude CLI, shelled out to from the run loop. Sonnet rather than the largest model available: the task is small, bounded and heavily evidenced, and a wrong proposal costs one rehearsal on a throwaway copy. |
| What it can reach | Nothing. --tools "" leaves it no tools and --strict-mcp-config stops it acquiring more, so the brief is the entire evidence base by construction rather than by instruction. It runs in an empty temporary directory, not the checkout, so no project file or local configuration reaches the prompt — and with no filesystem, shell or network, a diagnostician that wanted to look at the failing data itself could not. |
| When it runs | Once, on a run a hard control has already blocked, and not at all otherwise — a clean run makes no model call. If an escalation is already open for the same trigger it is skipped too, because asking again about a fault a person has been told about produces the same answer at the same price. Its stdin is closed so it can never wait on a prompt under a timer, and it is given 120 seconds: a blocked run is already late, and escalating beats waiting five minutes for a diagnosis. |
| What its answer is worth | Nothing on its own. The reply is parsed into one of the five actions above, and anything that will not parse — prose, a sixth action, a malformed argument — is an escalation rather than a retry. The proof gate then rehearses the action against a byte copy of the warehouse and re-runs all 34 controls before anything is committed. |
| What it costs | Nothing per run. It authenticates against the operator’s Claude plan rather than a metered API key, which is also why the service runs as the operator’s own user instead of an isolated service account — plan credentials do not survive that privilege separation. That is a real trade-off, taken deliberately: the alternative is a recurring bill on a job that is idle whenever the pipeline is healthy. |
12 · Export — and the one rule the console obeys
the dashboard renders; it never derivesThe last stage writes a single file, pipeline.json, and the console reads it and draws it. The web tier holds no database connection, makes no network call, reaches no model, and performs no arithmetic that changes a figure — money is divided by a hundred once, when an integer number of cents becomes a string. That is not a style preference. A number computed at render time is a number no check ever saw, and the whole claim of this project is that what is on the screen was verified before the screen existed. A test fails the build on the commit that introduces a fetch, a database import, or a fourth runtime dependency. Two consequences you can see: the export is re-read on every request rather than imported, so the page cannot freeze at the moment it was built; and identities are quoted verbatim from the control that proved them rather than recomputed here, because a page that recalculates the identity it claims to have verified has verified nothing. A run that fails a hard check writes no figures at all — it writes only a health file saying so, which is what the banner at the top of these pages reads when the pipeline is blocked.
13 · Checking the checker
./verify.py — 13 claims, sharing no code with the pipelineEverything above is the pipeline marking its own homework. So a second program reads the same delivered files with the standard library, reaches its own totals, and compares them to what was published. It imports nothing from the pipeline — a test asserts that structurally, by parsing its imports — which means it had to re-derive every format decision independently: that Stripe reports integer cents while everything else is decimal dollars, that a Square gross includes the tip, that refunds are ordinary negative rows, and that seven status words across two booking systems collapse to five outcomes. A verifier reusing the pipeline’s parsers would inherit its bugs and prove only that the code agrees with itself. It exits 0 when every claim holds, 1 naming the claim that does not, and 2 when the landing zone has changed since the export was published — the emitter runs on its own timer, and comparing across that boundary would report a difference that is not an error, which is the most expensive kind of false alarm because it looks like a finding. What it deliberately does not verify is revenue recognition itself: re-deriving that would be a second implementation rather than a check, and it says so in its own output rather than letting you assume otherwise.
14 · Why the clock in the sidebar is not today
Two timers run every fifteen minutes. The first plays the part of the seven source systems: it advances a business clock by six hours and re-exports the current month’s files, the way a great many real export jobs behave. The second runs the pipeline over whatever it finds. One simulated day passes per real hour, so the pipeline is genuinely processing data it has not seen before rather than re-reading a frozen folder and skipping everything — which would be unattended in name only.