Home › Use Cases › Audit a marketing send file against the suppression register
Use caseUC0412
🧪 Use-case kit · runnable

Audit a marketing send file against the suppression register

A small, forkable project that does one job end to end. Run once for real, and every figure on these pages captured from that run.

The business caseThe problem this solves

A casino's suppression register is the record of who may not be marketed to — patrons who self-excluded, patrons who asked for a cooling-off period, patrons the property banned itself from contacting, and patrons who withdrew permission for one channel. The register only means anything if somebody checks it against what actually went out. That check is a person with two systems open: the delivery log the send platform exported, and the register as it stood — and for every contact on the log they have to decide two things no query can decide for them. Whether the registration was actually standing on the day that message went out, which the register note records and the Status column does not; and whether the registration as WRITTEN reaches that channel and that class of message, which the note writes out and the coarse Scope code cannot. A campaign is tens of thousands of contacts. Nobody reads them all, so the check becomes a sample, and a sample is exactly the wrong instrument for a population where one row is the whole finding. the manual pass a responsible-gaming desk makes over a closed campaign's delivery log against the suppression register, one contact at a time

Audience

The responsible-gaming or marketing-permissions desk that has to say, after a campaign has closed, whether any contact on it reached a patron the register covered — and how many individual messages did. It is also for anybody deciding whether this job needs a model at all, because on this corpus the honest answer is a qualified no: free column code is not separable from the paid call overall. Every number on these pages came from one real run of this code, not from a vendor page.

The inputThe actual send files

The corpus is 62 send files, 0.18 MB (txt 62). A suppression register is the most sensitive record a casino holds about a person, so there is no real corpus that could be published and none was sought. What is generated instead is the SHAPE of the job: a coarse Status column that the register note overrides in both directions, a coarse Scope code that a note narrows by writing channels out — sometimes in the register's own English rather than the table's tokens — a message class the register has no column for at all, and a date comparison (S-7) that decides whether the audience pull is a defence. Two in five ordinary notes carry a second sentence with a date, a channel list, or both, that means nothing, so a reader cannot treat the presence of a note as the presence of a narrowing.

The corpus

  • The 62 send filesgenerated from a fixed seed, so no real record, person or institution appears in it.
  • Where each came fromwritten for this kit rather than collected — the corpus is generated in the kit's own repository, so there is no third-party data in it.

Swap this folder for your own material and the kit is pointed at your send files. That is the whole change — there is no database to migrate.

One send file, as the model receives itSND-0001.txt · 1 of 62
SEND FILE HEADER
  Send file            SND-0001
  Campaign             CMP-2026-0100 - loyalty statement mailing
  Property             PRP-03
  Audience pulled      2026-08-01
  Flight               2026-08-08 to 2026-08-12
  Register revision    2026-08/R2, re-issued 2026-07-30
  Prepared by          marketing operations desk

SUPPRESSION STANDARD EXTRACT (SUP-2026)
  S-2  Find the register entry for the patron reference. No entry, no suppression.
  S-3  A registration reaches a contact only if the contact's own Sent date falls inside the
       entry's Registered from and Closes dates, inclusive of both ends.
  S-4  The Status column is the register owner's coding. The register note is where a lift, a
       withdrawal and a re-registration are recorded, each with its own date.
  S-5  The Scope column is a coarse class. The register note is where the channels are written
       out, and a note that lists the channels a registration covers excludes every channel it
       does not name.
  S-6  A registration suppresses every message class unless its note says otherwise. Only
       statutory account notices and tier-service messages are ever exempted.
  S-7  A registration that took effect after the audience was pulled and on or before the day the
       contact went out still reaches that contact.
  S-9  A breaching row contributes its own Touches figure; a permitted row contributes nothing.

CHANNEL AND CLASS TABLE
   Scope code         admits these channels
   all-channels       email, sms, push, direct-mail, phone
   electronic-only    email, sms, push
   postal-only        direct-mail
   phone-only         phone
   Message classes    free-play-offer, cash-offer, event-invite, tier-service, account-notice

SUPPRESSION REGISTER EXTRACT

Abridged — the file continues.

The outcomeWhat a good result looks like

Every contact on the send file carries a verdict, the kind of registration it rests on, the number of messages at risk and one row copied verbatim as evidence; the file carries REPORTABLE or CLEAN, the exact list of breaching rows, and a total. A desk opens the exception list and works it, rather than sampling a delivery log.

And when it cannot

It clears breaches. On the scored run 24 of 273 contacts that reached a covered patron were called PERMITTED, and 3 contacts nothing suppressed were flagged. The first number is the one that matters and the kit publishes it first: a cleared breach is a marketing message to a self-excluded patron that nobody will ask about again.

Where it fitsWhat did work

Every line below is a measured result from this kit's own runs, with the figure that supports it. The headline above is not softened by any of them.

  • your register's Status column is maintained in real time and is never stale — the free column floor (evals/baseline.py --floor rules)
    every remaining question is a patron lookup and three date comparisons; it scores 251 of 273 contacts here for $0.00 and it is not separable from the paid call
  • your register notes are written in a handful of house phrasings you can enumerate — a keyword floor over the note (--floor phrase), written once and kept
    on this corpus that floor solves the WHOLE thing — 273 of 273 contacts, 62 of 62 send files, $0.00
  • your register notes are free prose written by whoever was on the desk — the paid call, on the contacts whose patron HAS a register entry
    the 22 contacts whose answer is only in the note are where it wins and where it wins significantly: 18 of 22 against the column floor's 0, p = 0.000008
  • you need the exception list to be defensible to a regulator — the paid call plus the pure-code station, and read the citation column
    every breach carries one row copied verbatim out of the delivery log, located in it and scored; 235 of 273 citations were credited and 0 could not be located at all

At a glanceHow the whole thing runs

90%contact verdict, exact, after the station
2,385 msp50, end to end
$0.00per 1,000 send files · this tier, off-peak

Run once, for real, on 2026-09-11. Every figure on these pages was captured from that run — nothing is written from intent.

14 steps, grouped by the question that sends you to them rather than by build order. Each tile carries the one figure that step is about, and opens the page behind it.

Should you use this?What you bring, where it stops, and when not to use it

Before you commit an afternoon to this, these are the answers that decide it. Each one is rendered from the record it lives in — and links the page that holds it in full.

What do I have to bring?Replace data/corpus/*.txt with your own delivery logs in the same six-column shape, data/register.json with your own register (patron_ref, kind, from, until, scope, status — and NO note; the note lives only in the rendered extract and only in the answer key), and data/gold.jsonl with your own labels. EVERY MEASURED NUMBER ON THIS PAGE STOPS BEING TRUE THE MOMENT YOU SWAP THE CORPUS. Corpus lens →
When is this the wrong choice?Avoid: Paying for a model to read a column that is already right. That is the case against the best-fitting scenario (“your register's Status column is maintained in real time and is never stale”). 4 scenarios scored in all, each with its own. Eval lens →
Where does it stop working?a delivery log whose contact rows are not the six fixed columns — src/rules.py's ROW_RE is keyed on the shape, so a seventh column or a re-ordered one parses to nothing and the file scores as a file with no contacts on it. 6 recorded failure modes, each from a run rather than a guess. Corpus lens →
What was never verified?WHETHER THE PAID CALL WOULD BEAT FREE CODE ON A REAL REGISTER. Every note in this corpus was written by tools/build_corpus.py in one of a dozen phrasings; a real register's notes are prose written by whoever was on the desk, and the direction that would move the numbers is unknown — richer prose is harder for the regex floor AND harder for the model. 5 items this kit says it could not check. Eval lens →
Can I run this on a model I control?Yes — any OpenAI-compatible endpoint, including one on your own hardware. The shipped adapter takes its host from BASE_URL and its model from MODEL, so nothing in src/ changes. The published figures come from 1 model on the fast tier, one provider, one key. Prompt lens →
And if it fits — what do I stand up?6 artifacts with a stated home and a stated egress, and 4 decisions each with what you provision past its ceiling — plus what was not measured. That is the next page, not this one. step 14 — Run it in your environment →

Not asked of this kit — 2 questions: clone (a fresh clone of this kit runs with nothing fetched); judge (nothing here is graded by a model).

Last verified 2026-09-11 — r001-suppression-audit. Every figure on these pages was captured from that run.

Run itHow this reaches your data

Every result on this page was produced by pure code over checked-in files, with no API key — which is why you can read the numbers before anyone spends anything.

Run this on your own data

  • The pipeline, its eval harness and the runs behind every numberdeployed inside your environment, on your own model endpoints, against your own documents.
  • The corpus above is the shape, not the limitit is a folder swap, and there is no database to migrate.

Talk to us →

Checked before this shipped — a clean checkout with no key configured renders the whole board — all 62 send files, the register extracts, SUP-2026, all four free floors computed live, the recorded paid run replayed off its committed result file, the citation highlighting and the corpus tab — and scores the free floors offline at $0.00. The 'Check with the model' control is disabled and the reason is printed on the page beside it. Measured on 2026-09-11 by tools/shoot_ui.mjs, which blanks API_KEY before it starts the server.

A living map of modern AI — kept current every morning