From 543e49091cc5499139fe08ba80de67f241cbf86f Mon Sep 17 00:00:00 2001 From: Santiago Alvarez Rodriguez Date: Wed, 5 Aug 2026 18:32:04 -0500 Subject: [PATCH] Fix where the brief and the exercise data disagree A dry run through the exercise surfaced five claims in the README and sources.yml that the repo doesn't deliver, and one thing candidates are never told: how long they have. The data edits are three cells, not new rows. The dataset is small enough to read in full during the session, and it should stay that way. - Session length is now stated: 60 minutes. - A Meridian order carries a real grouping separator, so "thousands/decimal separators" is true of the data and not just the brief. - Email now lines up across the two systems in more than one shape, so cross-source identity is a decision rather than a single exact match. - customer_id is documented as living only on pages; the money keys on account_id. - The mid-session ingest step is announced the way the add-partner step is. - Vendor names dropped from source descriptions; the exercise shouldn't imply a stack. --- .gitignore | 2 ++ README.md | 14 ++++++++------ data/initial/meridian_customers.csv | 4 ++-- data/initial/meridian_orders.csv | 2 +- dbt/models/sources.yml | 8 ++++---- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index f4427fa..acf9443 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 0118749..9a47ede 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: @@ -56,7 +56,7 @@ 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. @@ -64,6 +64,8 @@ Meridian's raw data lands in the same `raw` schema as TTG's (see `dbt/models/sou 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. @@ -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. diff --git a/data/initial/meridian_customers.csv b/data/initial/meridian_customers.csv index 8bbf120..6f6a26a 100644 --- a/data/initial/meridian_customers.csv +++ b/data/initial/meridian_customers.csv @@ -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 diff --git a/data/initial/meridian_orders.csv b/data/initial/meridian_orders.csv index 4f9cca4..62132cf 100644 --- a/data/initial/meridian_orders.csv +++ b/data/initial/meridian_orders.csv @@ -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 diff --git a/dbt/models/sources.yml b/dbt/models/sources.yml index b1d1a2f..e9c4ece 100644 --- a/dbt/models/sources.yml +++ b/dbt/models/sources.yml @@ -59,7 +59,7 @@ 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 @@ -67,7 +67,7 @@ sources: - 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). @@ -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). @@ -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