Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# dbt
dbt/target/
dbt/logs/
dbt/dbt_packages/
dbt/package-lock.yml

# Optional fast-reset dump (generated by scripts/capture_initial_state.sh)
data/initial_state.dump
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ For this round, you'll take on **Meridian Live**, a fictional (but realistic) MA

There are two challenges in this repo. **Challenge 1** is onboarding Meridian — modeling judgment against messy source data. **Challenge 2** is the whitelabel page-tracking sources — a pipeline-design problem. Both hand you a project rather than a task: what the platform should grow on top of what's here is the thing you're working out, including which of the two is worth your time and how far to take it.

The exercise will be completed **live with the interviewer(s)**. You'll work locally using this repo. You may use your normal tools, including AI assistants.
The exercise will be completed **live with the interviewer(s)**, in **60 minutes**. You'll work locally using this repo. You may use your normal tools, including AI assistants.

**This exercise is about architecture and judgment, not finishing everything.** You are not expected to model every Meridian table or handle every edge case in the time provided. We care more about how you reason through the tradeoffs, what you flag as a risk or open question, and how you'd sequence the work than about raw completion. Talk through your thinking as you go — this is as much a design conversation as a coding one.
**This exercise is about architecture and judgment, not finishing everything.** Sixty minutes is not enough to model every Meridian table or handle every edge case, and it isn't meant to be. We care more about how you reason through the tradeoffs, what you flag as a risk or open question, and how you'd sequence the work than about raw completion. Talk through your thinking as you go — this is as much a design conversation as a coding one.

## What's already built vs. what you'll build

Expand All @@ -37,14 +37,14 @@ docker compose up -d
./scripts/init.sh
```

Verify dbt works and the existing platform builds cleanly:
`init.sh` finishes by running the existing models. Confirm dbt works from the shim too, and that a second run is still clean:

```bash
bin/dbt --version
bin/dbt run
```

If these commands complete successfully, you're ready for the interview.
If these commands complete successfully, you're ready for the interview. On Apple Silicon the dbt image runs emulated, so make sure Rosetta is enabled in Docker Desktop (Settings → General) before you get here — it's the one thing that fails on the day rather than now.

To reset the warehouse to its initial state at any time:

Expand All @@ -56,14 +56,16 @@ To reset the warehouse to its initial state at any time:

Meridian's raw data lands in the same `raw` schema as TTG's (see `dbt/models/sources.yml`), but it isn't a clean drop-in — that's the point. A few problems you'll likely need to reckon with, in no particular order:

- **Identity across systems.** TTG's unstable `customer_id` is resolvable through the `identity_merges` log. Meridian has no such log, and there's no shared key between TTG and Meridian besides a loosely-formatted email. Some real people plausibly exist in both systems. How would you approach unifying (or deliberately not unifying) identity here, and what are the failure modes of your approach?
- **Identity across systems.** TTG has two ids for a person: a stable `account_id` that the money keys on, and an unstable `customer_id` that only `pages` carries and only the `identity_merges` log can resolve. Meridian has no such log, and the two systems share no key at all besides a loosely-formatted email. Some addresses appear on both sides. How would you approach unifying (or deliberately not unifying) identity here, and what are the failure modes of your approach?
- **Currency.** Meridian orders are priced in GBP, EUR, and SEK, formatted inconsistently (symbols, thousands/decimal separators). TTG's amounts are USD. How do you normalize without hardcoding rates in SQL?
- **Grain.** TTG keeps the checkout and the money in separate tables: `orders` is one row per checkout, `transactions` is one row per payment taken against it, and payment is the grain TTG's facts sit at. Meridian has no payments table. The money lives on the order row itself — currency, subtotal, fees, total, status — and `meridian_order_items` splits that same money into line items with their own price and quantity. A Meridian order is therefore not a TTG transaction and not a TTG order either: it's payment detail recorded at checkout grain, with a finer grain underneath it. Read `stg_orders` and `stg_transactions` next to `meridian_orders` and `meridian_order_items` before you commit to anything. Sooner or later someone sums an amount across both sources, and what they get back depends on the grain you chose.
- **Time.** Meridian stores performance start times as naive local timestamps plus a UTC offset in minutes; TTG's `showtimes.start_at` is already a normalized instant. Reconcile these consistently.
- **Order status and cancellations.** Meridian orders can be `paid`, `refunded`, `partial_refund`, or `cancelled` (casing/whitespace inconsistent). What should "revenue" mean once these exist, and does that change what belongs in the fact you expose versus what a stakeholder should query separately?

You don't need to resolve every one of these perfectly. Pick a defensible position on each, implement what you can in the time available, and be ready to explain what you didn't get to and why.

Mid-session your interviewer will run `bin/ingest`, which appends a new batch to the source tables the way the real pipeline receives one, and ask you to rebuild. Nothing you've built has to be finished by then — but what is built should still be right afterwards, and you should be able to say how you'd know if it weren't.

## Challenge 2: whitelabel storefronts that onboard themselves

TTG powers whitelabel storefronts for other MARI brands. Each brand's web tracking lands in its own schema in the warehouse, next to `raw`. Nothing in this project reads them — `stg_pages` covers TodayTix's own pages and stops there. Page loads across TodayTix and the brands are meant to be one stream, and today they aren't.
Expand Down Expand Up @@ -122,7 +124,7 @@ TTG (existing, already handled in staging — for reference):
- Sentinel nulls (`N/A`, `NULL` string, empty string) on optional FKs in `pages`.

Meridian (new, unhandled):
- **Currency formatting** – `£120.00`, `"95,00 €"` (European decimal comma + symbol), `"1050,00 kr"`, or plain `100.00`, all within the same column.
- **Currency formatting** – `£120.00`, `"95,00 €"` (European decimal comma + symbol), `"1.050,00 kr"` (grouping and decimal separators both inverted), or plain `100.00`, all within the same column.
- **Casing/whitespace** – event categories, order statuses, and page types all vary in casing and padding.
- **Sentinel-ish nulls** – blank, `N/A`, `NULL` string on optional identity/FK fields, same pattern as TTG's `pages` but on different tables.
- **Duplicate identity within Meridian itself** – at least one real person has two `customer_id`s in `meridian_customers` with matching email but slightly different name formatting.
Expand Down
4 changes: 2 additions & 2 deletions data/initial/meridian_customers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ MC-1006,Alice Whitfield,alice.whitfield@example.com,+44 20 7946 0958,United Kin
MC-1007,Farid Haddad,farid.haddad@example.com,+33 1 4000 0000,France,2025-01-25T16:10:00,false
MC-1008, Grace Lin ,grace.lin@example.com,+44 20 1234 5678,UK,2025-02-01T12:00:00,true
MC-1009,,unknown_guest_09@example.com,,UK,2025-02-03T13:00:00,false
MC-1010,Ivy Chen,ivy.chen@example.com,+44 161 000 0000,uk,2025-02-05T15:45:00,TRUE
MC-1010,Ivy Chen, User7@Example.com ,+44 161 000 0000,uk,2025-02-05T15:45:00,TRUE
MC-1011,Jonas Weber,jonas.weber@example.com,+49 30 1234 5678,Germany,2025-02-08T10:00:00,false
MC-1012,Karolina Nowak,karolina.nowak@example.com,+48 22 000 0000,Poland,2025-02-10T09:15:00,true
MC-1013,Liam O'Sullivan,liam.osullivan@example.com,+353 1 234 5678,Ireland,2025-02-12T14:30:00,false
MC-1014,Maya Patel, maya.patel@example.com ,+44 20 7000 0000,UK,2025-02-15T11:20:00,TRUE
MC-1015,Noah Bergstrom,noah.bergstrom@example.com,+46 8 000 0000,Sweden,2025-02-18T17:00:00,false
MC-1015,Noah Bergstrom,user4@example.com,+46 8 000 0000,Sweden,2025-02-18T17:00:00,false
MC-1016,Sam Reyes,user4@example.com,+44 20 5000 0000,UK,2025-02-20T09:00:00,true
2 changes: 1 addition & 1 deletion data/initial/meridian_orders.csv
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mo_14,MC-1014,mp_12,GBP,122.00,8.00,130.00,Paid,2025-01-20T15:50:00
mo_15,MC-1001,mp_3,GBP,£135.00,£9.00,£144.00,paid,2025-01-22T10:05:00
mo_16,MC-1016,mp_2,GBP,£128.00,£8.50,£136.50,paid,2025-01-15T09:20:00
mo_17,MC-1002,mp_5,GBP,99.00,6.50,105.50, cancelled ,2025-01-18T08:40:00
mo_18,MC-1015,mp_6,SEK,"1050,00 kr","70,00 kr","1120,00 kr",paid,2025-01-27T10:00:00
mo_18,MC-1015,mp_6,SEK,"1.050,00 kr","70,00 kr","1.120,00 kr",paid,2025-01-27T10:00:00
mo_19,MC-1003,mp_10,EUR,100.00,6.50,106.50,paid,2025-02-09T14:20:00
mo_20,,mp_7,GBP,50.00,0.00,50.00,paid,2025-02-01T20:00:00
mo_21,MC-1013,,GBP,25.00,0.00,25.00,paid,2025-01-29T09:00:00
8 changes: 4 additions & 4 deletions dbt/models/sources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ sources:
- name: account_id
description: Stable; correlates to accounts.
- name: customer_id
description: Unstable; may be merged at any time (e.g. Segment Unify). Resolve via identity_merges when building marts.
description: Unstable behavioural id, emitted by the tracker and merged at any time. Only pages carries it — orders and transactions key on account_id. Resolve via identity_merges when building marts.
- name: page_type
- name: occurred_at
- name: event_id
description: Optional context (e.g. viewed product).
- name: showtime_id
description: Optional context (e.g. viewed showtime).
- name: identity_merges
description: Merge log for TTG customer_id (e.g. Segment Unify). from_customer_id was merged into to_customer_id at merged_at. Use to resolve pages.customer_id to canonical id when building marts.
description: Merge log for TTG customer_id. from_customer_id was merged into to_customer_id at merged_at. Appended to over time, like any other source. Use to resolve pages.customer_id to canonical id when building marts.
columns:
- name: from_customer_id
description: Id that was merged away (unstable/deprecated).
Expand All @@ -77,7 +77,7 @@ sources:

# ---- Meridian Live (new MARI portfolio company) — onboarding target for this exercise ----
- name: meridian_customers
description: Meridian's account entity. No relationship to TTG accounts is provided — a shared real-world person may exist in both systems with no shared key other than (unreliable) email.
description: Meridian's account entity. No relationship to TTG accounts is provided, and email is the only field the two systems have in common — an address that appears in both is a hint, not a key.
columns:
- name: customer_id
description: Primary key, Meridian format (e.g. MC-1001).
Expand Down Expand Up @@ -128,7 +128,7 @@ sources:
- name: performance_id
description: Optional FK to meridian_performances; blank for non-ticket orders (e.g. merchandise).
- name: currency
description: ISO-ish currency code (GBP, EUR, SEK, USD).
description: ISO-ish currency code (GBP, EUR, SEK). None of Meridian's trade is in USD.
- name: subtotal
- name: fees
- name: total
Expand Down