← Back to featured work

Casino Analysis

The problem

Casino marketing runs on questions. Whether an offer earned what it cost. Whether a quarter is worth the ships we put into it. What one version of a campaign actually returned against what we would have taken anyway.

Each question used to get answered once. Someone pulled the numbers, the answer went into a slide or an email, and the working was gone. Six months later the same question came back, and nobody could say how the first answer was reached or whether it still held.

Decks had the same problem from the other side. A chart got built once from a pull. The data moved. The chart did not.

How it is organised

One directory per question. Each one records four things.

The questionThe answerThe command to re-run itThe data it came from

There are thirty two directories in it today. Some are closed and kept as the audit trail. Others are live tools, because the question is not a one off: it comes back every month, every quarter, or every campaign.

The scripts say what they are by their name. A probe investigates. A pull extracts. A build constructs the deliverable. The probes and their output are kept on purpose, long after the answer is settled, because a year later how a number was reached matters more than the number.

What it answers

Campaign and offer

  • How a campaign version performed
  • Whether an offer earned its cost
  • Which sailings to put an event behind
  • Whether a comped offer moved to another guest

Season and itinerary

  • How one quarter compares with another
  • How a region trades against the rest
  • How an unusual voyage length behaves
  • Where the fleet is, day by day

Planning and spend

  • What a budget case rests on
  • How the channel mix is moving
  • What a send is worth
  • How volume is pacing against last year

& more

Slides that cannot drift from their data

Some of these answers live in a deck that goes out every month. Those slides are not screenshots of an old analysis. They are rebuilt from the database each cycle by a script.

01

Read the view

Pull the measure and reconcile it before anything is written.

Snowflake

02

Write the workbook

The query, the table it produced, and the chart it drives, all on one tab.

openpyxl

03

Build the slide

Charts linked back to the workbook, and the slide found by its title, never its position.

python-pptx

04

Leave the working behind

The same query and the same table go into the speaker notes, with the traps and the command to rebuild.

Notes

05

Run it again next cycle

The slide follows the data instead of following the last version of the deck.

Monthly

The rule is that every slide carries its query, its data and its chart. Anyone can audit the number a year from now without re-deriving it. Slides get found by title because positions move the moment someone inserts a slide, and a stale position points at the wrong slide without complaining.

Judging a campaign

A campaign goes out in versions: same campaign, different offer, different guests. The question afterwards is easy to ask and hard to answer. Did this version do better than what we would have taken anyway?

There is a Streamlit app in the repo that answers it. It isolates one version and puts it next to the rest of the market on the same sailings, live from the database.

Its own sailingsThe guests who took itThe market beside itPer berth, per day

It starts from the sailings that version was actually built on, and the bookings that took that version’s offer. Versions share sailings, so a version is isolated by the list the guest was targeted under, not by the sailing. The comparison group is every booking on those same sailings that came in with no casino or onboard spend offer attached. Both sides are measured per occupied berth per day, so a large cohort and a small one can sit side by side. Profit is ticket revenue net of what the comp cost, plus what the guest went on to spend onboard.

The rest of the app is mostly about not being misled.

The comparison moves with the sailings you select, and that is deliberate. Select everything and every version is measured against one yardstick. Select a single version’s sailings and it gets its own market, which does not charge it for sailings it never ran.

Before a campaign sails there are no actuals, so it switches to the at booking expectation, and it refuses to show a market comparison in that mode. The market is never scored that way, and the percentage would be fabricated. Figures that do not exist yet are left blank rather than set to zero, because a zero reads as a failed campaign when the truth is that it has not sailed.

It shows the tier system the campaign was actually targeted on, not whichever column happens to hold more data. If the right column is empty it shows nothing and says why. It flags how much of a campaign has returned, and it flags a single booking large enough to move a small version’s average on its own.

It runs inside Snowflake rather than on a laptop, so there is nothing to install and no credential to hand around. The analysis functions have a test suite that runs with no database at all, and a locked output from a real campaign gets diffed on every change, so a formula that quietly shifts is caught before it ships.

Outcome

The value is not any single answer. It is that every answer has its working attached, the recurring ones rebuild themselves from the database rather than from the last version of the deck, and a question that comes back is re-run rather than re-derived.