Queries · q374 · Head of Lifecycle Marketing · week 2, night 2
“Is the large average-order-value gap seen among Apparel members (28-40% lower than non-members across every tenure band) explained by heavier discounting at checkout, or do members simply buy cheaper products before any discount is applied?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
Week 1; shared with Tobias Lindqvist, Chief Financial Officer
Is the loyalty and referral programme worth it? Do members and referred customers behave differently from others, and what does the programme cost?
The manager, at the start of week 2
Subject: Re: Loyalty & referral — thank you, and where to dig next
Ingrid, this is excellent — especially catching that the refund-rate gap was a vertical mix artifact. Good instinct to go back and break that out rather than let the scary headline stand.
What surprised me most, and what I want explained before we take this to Tobias as a joint recommendation: a 19.4% cost-to-member-revenue ratio is strikingly high, and members showing lower revenue and order counts than non-members is the opposite of what a loyalty programme is supposed to produce. I don't want to present "the programme loses money" to leadership until I understand whether that's a true causal effect or another mix artifact like the refund rate turned out to be — since 72% of members sit in one vertical, I'm wary of a second apples-to-oranges comparison hiding in there.
Questions for this week, in my own words:
The B2B extension question can wait — I agree we shouldn't open that door until we trust the member economics number. Let's keep that out of this week's slots unless there's room left over.
Thanks again, Ingrid — this is exactly the kind of rigor I want from this team.
Best, Aisha
The analyst · week 2, night 2, request 2 of 3
Night 1 answered Aisha's Q1 cleanly: within Apparel and controlling for tenure, order counts and refund rates between members and non-members are essentially indistinguishable in every tenure band (refund rates all cluster 33-40% regardless of segment) - the refund-rate and order-frequency 'penalty' really was mostly mix artifact. But a new, robust finding emerged: average order value is 28-40% lower for members than non-members in every single tenure band within Apparel - consistent and large, so that's the real driver of lower member revenue, and it needs explaining before I call it a verdict. Report 2 answered Q2 well: redemption rate rises steadily with cohort age (4% for 2-month-old cohorts up to ~32-37% for cohorts over 2 years old) and then plateaus around that 32-37% level even for the oldest 2023 cohorts - so it's both a timing lag (explains why young cohorts drag the blended rate down) and a structural ceiling (most points, even given 3 years, never get redeemed) - meaning the liability will keep growing in euros as we add members, while each cohort's own eventual redemption converges to roughly a third of what's issued. Report 3 gave a clean, important negative result for Q3: order_id is populated on 100% of 'earn' transactions but 0% of 343 'burn' transactions - the data model genuinely never records which order (if any) a redemption applied to, so the order_id-based gaming test was never going to work; I need a different join (same-user, same-day proximity) before I can call this inconclusive for good. Tonight: (1) retry the gaming test using date-proximity matching instead of order_id; (2) decompose the Apparel AOV gap into subtotal vs discount to see if heavier discounting (consistent with points use) explains it; (3) start on Q4 by comparing top-revenue members against the rest of the member base to see if better performers use the programme differently.
The engineer · 1 dry run on the empty schema, then the submission
WITH apparel_orders AS ( SELECT o.id, o.tenant_id, o.user_id, o.currency_code, o.subtotal, o.discount_total, o.grand_total, o.placed_at FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN users u ON u.id = o.user_id WHERE t.industry = 'Apparel & Footwear' AND u.is_staff = false AND o.status <> 'cancelled' AND o.placed_at >= DATE '2023-01-01' AND o.placed_at < DATE '2026-01-01' ), user_segment AS ( SELECT ao.*, CASE WHEN la.user_id IS NOT NULL THEN 'loyalty member' ELSE 'non-member' END AS segment FROM apparel_orders ao LEFT JOIN (SELECT DISTINCT user_id FROM loyalty_accounts) la ON la.user_id = ao.user_id ), fx_converted AS ( SELECT us.segment, us.subtotal / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS subtotal_eur, us.discount_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS discount_total_eur, us.grand_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS grand_total_eur FROM user_segment us LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = us.currency_code AND er.as_of = CAST(us.placed_at AS DATE) ), segment_agg AS ( SELECT segment, COUNT(*) AS order_count, AVG(subtotal_eur) AS avg_subtotal_eur, AVG(discount_total_eur) AS avg_discount_total_eur, AVG(grand_total_eur) AS avg_grand_total_eur, SUM(discount_total_eur) AS sum_discount_eur, SUM(subtotal_eur) AS sum_subtotal_eur FROM fx_converted GROUP BY segment ) SELECT segment, order_count, ROUND(avg_subtotal_eur, 2) AS avg_subtotal_eur, ROUND(avg_discount_total_eur, 2) AS avg_discount_total_eur, ROUND(avg_grand_total_eur, 2) AS avg_grand_total_eur, ROUND(100.0 * sum_discount_eur / NULLIF(sum_subtotal_eur,0), 2) AS discount_rate_pct FROM segment_agg ORDER BY CASE WHEN segment = 'loyalty member' THEN 0 ELSE 1 END, segment
“Query compiles and returns the expected columns.”
“Submitted. The report computes, per segment (loyalty member vs non-member) within Apparel & Footwear tenants, the order count and EUR-converted averages of subtotal, discount total and grand total, plus the aggregate discount rate (sum of discounts ÷ sum of subtotals) — which directly separates "heavier discounting" from "cheaper pre-discount baskets" as the driver of the AOV gap.”
As Ingrid Solberg saw it, from the small dataset the agents worked on
segment | order_count | avg_subtotal_eur | avg_discount_total_eur | avg_grand_total_eur | discount_rate_pct ---------------+-------------+------------------+------------------------+---------------------+------------------ loyalty member | 3411 | 150.48 | 6.97 | 171.92 | 4.63 non-member | 12795 | 183.56 | 6.61 | 196.75 | 3.6 (2 rows)
The memo and the queries that followed; links found by title, so inferred
It builds on:
Changed only where the benchmark needs the same result on every system
WITH apparel_orders AS ( SELECT o.id, o.tenant_id, o.user_id, o.currency_code, o.subtotal, o.discount_total, o.grand_total, o.placed_at SELECT o.id AS id, o.tenant_id AS tenant_id, o.user_id AS user_id, o.currency_code AS currency_code, o.subtotal AS subtotal, o.discount_total AS discount_total, o.grand_total AS grand_total, o.placed_at AS placed_at FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN users u ON u.id = o.user_id WHERE t.industry = 'Apparel & Footwear' AND u.is_staff = false AND o.status <> 'cancelled' AND o.placed_at >= DATE '2023-01-01' AND o.placed_at < DATE '2026-01-01'),user_segment AS ( SELECT ao.*, CASE WHEN la.user_id IS NOT NULL THEN 'loyalty member' ELSE 'non-member' END AS segment FROM apparel_orders ao LEFT JOIN (SELECT DISTINCT user_id FROM loyalty_accounts) la ON la.user_id = ao.user_id),fx_converted AS ( SELECT us.segment, SELECT us.segment AS segment, us.subtotal / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS subtotal_eur, us.discount_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS discount_total_eur, us.grand_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS grand_total_eur FROM user_segment us LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = us.currency_code AND er.as_of = CAST(us.placed_at AS DATE)),segment_agg AS ( SELECT segment, COUNT(*) AS order_count, AVG(subtotal_eur) AS avg_subtotal_eur, AVG(discount_total_eur) AS avg_discount_total_eur, AVG(grand_total_eur) AS avg_grand_total_eur, SUM(discount_total_eur) AS sum_discount_eur, SUM(subtotal_eur) AS sum_subtotal_eur FROM fx_converted GROUP BY segment)SELECT segment, order_count, ROUND(avg_subtotal_eur, 2) AS avg_subtotal_eur, ROUND(avg_discount_total_eur, 2) AS avg_discount_total_eur, ROUND(avg_grand_total_eur, 2) AS avg_grand_total_eur, ROUND(100.0 * sum_discount_eur / NULLIF(sum_subtotal_eur,0), 2) AS discount_rate_pct avg_subtotal_eur AS avg_subtotal_eur, avg_discount_total_eur AS avg_discount_total_eur, avg_grand_total_eur AS avg_grand_total_eur, 100.0 * sum_discount_eur / NULLIF(sum_subtotal_eur,0) AS discount_rate_pctFROM segment_aggORDER BY CASE WHEN segment = 'loyalty member' THEN 0 ELSE 1 END, segmentORDER BY CASE WHEN segment = 'loyalty member' THEN 0 ELSE 1 END NULLS LAST, segment NULLS LAST
WITH apparel_orders AS ( SELECT o.id AS id, o.tenant_id AS tenant_id, o.user_id AS user_id, o.currency_code AS currency_code, o.subtotal AS subtotal, o.discount_total AS discount_total, o.grand_total AS grand_total, o.placed_at AS placed_at FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN users u ON u.id = o.user_id WHERE t.industry = 'Apparel & Footwear' AND u.is_staff = false AND o.status <> 'cancelled' AND o.placed_at >= DATE '2023-01-01' AND o.placed_at < DATE '2026-01-01' ), user_segment AS ( SELECT ao.*, CASE WHEN la.user_id IS NOT NULL THEN 'loyalty member' ELSE 'non-member' END AS segment FROM apparel_orders ao LEFT JOIN (SELECT DISTINCT user_id FROM loyalty_accounts) la ON la.user_id = ao.user_id ), fx_converted AS ( SELECT us.segment AS segment, us.subtotal / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS subtotal_eur, us.discount_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS discount_total_eur, us.grand_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS grand_total_eur FROM user_segment us LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = us.currency_code AND er.as_of = CAST(us.placed_at AS DATE) ), segment_agg AS ( SELECT segment, COUNT(*) AS order_count, AVG(subtotal_eur) AS avg_subtotal_eur, AVG(discount_total_eur) AS avg_discount_total_eur, AVG(grand_total_eur) AS avg_grand_total_eur, SUM(discount_total_eur) AS sum_discount_eur, SUM(subtotal_eur) AS sum_subtotal_eur FROM fx_converted GROUP BY segment ) SELECT segment, order_count, avg_subtotal_eur AS avg_subtotal_eur, avg_discount_total_eur AS avg_discount_total_eur, avg_grand_total_eur AS avg_grand_total_eur, 100.0 * sum_discount_eur / NULLIF(sum_subtotal_eur,0) AS discount_rate_pct FROM segment_agg ORDER BY CASE WHEN segment = 'loyalty member' THEN 0 ELSE 1 END NULLS LAST, segment NULLS LAST
The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted
Report request from Ingrid Solberg (analyst for the Head of Lifecycle Marketing): Title: Apparel order value breakdown: subtotal, discount and total, member vs non-member Question: Is the large average-order-value gap seen among Apparel members (28-40% lower than non-members across every tenure band) explained by heavier discounting at checkout, or do members simply buy cheaper products before any discount is applied? Grain: one row per loyalty segment (member vs non-member), restricted to the Apparel and Footwear vertical Columns: - segment - order_count - avg_subtotal_eur - avg_discount_total_eur - avg_grand_total_eur - discount_rate_pct Details: Population: non-cancelled orders placed 2023-01-01 to 2025-12-31 by users (is_staff = false) belonging to tenants with tenants.industry = 'Apparel & Footwear'. Segment = 'loyalty member' if the user has a loyalty_accounts row, else 'non-member'. avg_subtotal_eur, avg_discount_total_eur and avg_grand_total_eur are averages of orders.subtotal, orders.discount_total and orders.grand_total respectively, each converted to EUR via exchange_rates (order currency_code to EUR, rate as_of placed_at). discount_rate_pct = (sum of discount_total in EUR) divided by (sum of subtotal in EUR) for the segment, as a percentage (an aggregate rate, not an average of per-order ratios). Order rows: 'loyalty member' first, then 'non-member'. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runWITH apparel_orders AS ( SELECT o.id, o.tenant_id, o.user_id, o.currency_code, o.subtotal, o.discount_total, o.grand_total, o.placed_at FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN users u ON u.id = o.user_id WHERE t.industry = 'Apparel & Footwear' AND u.is_staff = false AND o.status <> 'cancelled' AND o.placed_at >= DATE '2023-01-01' AND o.placed_at < DATE '2026-01-01' ), user_segment AS ( SELECT ao.*, CASE WHEN la.user_id IS NOT NULL THEN 'loyalty member' ELSE 'non-member' END AS segment FROM apparel_orders ao LEFT JOIN (SELECT DISTINCT user_id FROM loyalty_accounts) la ON la.user_id = ao.user_id ), fx_converted AS ( SELECT us.segment, us.subtotal / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS subtotal_eur, us.discount_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS discount_total_eur, us.grand_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS grand_total_eur FROM user_segment us LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = us.currency_code AND er.as_of = CAST(us.placed_at AS DATE) ), segment_agg AS ( SELECT segment, COUNT(*) AS order_count, AVG(subtotal_eur) AS avg_subtotal_eur, AVG(discount_total_eur) AS avg_discount_total_eur, AVG(grand_total_eur) AS avg_grand_total_eur, SUM(discount_total_eur) AS sum_discount_eur, SUM(subtotal_eur) AS sum_subtotal_eur FROM fx_converted GROUP BY segment ) SELECT segment, order_count, ROUND(avg_subtotal_eur, 2) AS avg_subtotal_eur, ROUND(avg_discount_total_eur, 2) AS avg_discount_total_eur, ROUND(avg_grand_total_eur, 2) AS avg_grand_total_eur, ROUND(100.0 * sum_discount_eur / NULLIF(sum_subtotal_eur,0), 2) AS discount_rate_pct FROM segment_agg ORDER BY CASE WHEN segment = 'loyalty member' THEN 0 ELSE 1 END, segment
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: segment, order_count, avg_subtotal_eur, avg_discount_total_eur, avg_grand_total_eur, discount_rate_pct
submitWITH apparel_orders AS ( SELECT o.id, o.tenant_id, o.user_id, o.currency_code, o.subtotal, o.discount_total, o.grand_total, o.placed_at FROM orders o JOIN tenants t ON t.id = o.tenant_id JOIN users u ON u.id = o.user_id WHERE t.industry = 'Apparel & Footwear' AND u.is_staff = false AND o.status <> 'cancelled' AND o.placed_at >= DATE '2023-01-01' AND o.placed_at < DATE '2026-01-01' ), user_segment AS ( SELECT ao.*, CASE WHEN la.user_id IS NOT NULL THEN 'loyalty member' ELSE 'non-member' END AS segment FROM apparel_orders ao LEFT JOIN (SELECT DISTINCT user_id FROM loyalty_accounts) la ON la.user_id = ao.user_id ), fx_converted AS ( SELECT us.segment, us.subtotal / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS subtotal_eur, us.discount_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS discount_total_eur, us.grand_total / CASE WHEN us.currency_code = 'EUR' THEN 1 ELSE er.rate END AS grand_total_eur FROM user_segment us LEFT JOIN exchange_rates er ON er.base_code = 'EUR' AND er.quote_code = us.currency_code AND er.as_of = CAST(us.placed_at AS DATE) ), segment_agg AS ( SELECT segment, COUNT(*) AS order_count, AVG(subtotal_eur) AS avg_subtotal_eur, AVG(discount_total_eur) AS avg_discount_total_eur, AVG(grand_total_eur) AS avg_grand_total_eur, SUM(discount_total_eur) AS sum_discount_eur, SUM(subtotal_eur) AS sum_subtotal_eur FROM fx_converted GROUP BY segment ) SELECT segment, order_count, ROUND(avg_subtotal_eur, 2) AS avg_subtotal_eur, ROUND(avg_discount_total_eur, 2) AS avg_discount_total_eur, ROUND(avg_grand_total_eur, 2) AS avg_grand_total_eur, ROUND(100.0 * sum_discount_eur / NULLIF(sum_subtotal_eur,0), 2) AS discount_rate_pct FROM segment_agg ORDER BY CASE WHEN segment = 'loyalty member' THEN 0 ELSE 1 END, segment
Submitted to tonight's batch. Your work on this request is done.