Queries · q201 · Support Operations Manager · week 1, night 2
“Is there a time-of-day or day-of-week pattern to when we breach first-response and resolution SLA targets?”
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 Daniel Achterberg, VP Customer Experience
Review how the helpdesk runs: queues and agent groups, use of macros and knowledge articles, SLA targets and breaches, and what makes tickets slow.
The manager, at the start of week 1
Subject: Helpdesk operating review — what I need this week
Hi Ben,
Daniel and I need a baseline read on how the helpdesk actually runs, so we can walk into our first leadership review with a clear picture instead of impressions. This feeds our decisions on how we staff/structure agent groups, where we invest in macros and knowledge base content, and whether our current SLA targets are realistic. Given you only get a handful of reports a night, here's what matters most, roughly in priority order:
No need to go deep on all five — pick what fits in the nightly slots and flag anything surprising even if I didn't ask directly. Keep it at a level Daniel and I can act on quickly; we'll dig deeper live once we see the numbers.
Thanks, Mia
Subject: A few things I'd like your review to cover
Hi Ben,
As you dig into the helpdesk review this week, a few questions from my side:
If you find anything that points to a specific merchant or category driving a disproportionate share of the pain, flag it even if it's outside these three.
Thanks, Daniel
The analyst · week 1, night 2, request 3 of 3
Night 1 surfaced a methodology problem before any business insight: Grace's agent-group report rolled up every tenant's same-named group (e.g. all 120 merchants' 'Customer Care') into one row, while Omar's SLA-breach-by-group report kept each tenant's group separate (171 rows). The two reports use different grains under the same label, so they can't be compared, and neither tells us whether load/staffing is balanced within a real operational unit (a tenant's own group). Also, the company briefing says a handful of merchants drive most volume — I haven't checked that at all yet, and Daniel explicitly asked to flag any single merchant driving disproportionate pain. So tonight I'm fixing the grain: first, ticket volume and SLA performance by merchant to find concentration and any outlier tenant; second, the highest-volume tenant+group combinations (the real staffing units) to redo the staffing-balance read properly; third, a day-of-week/time-of-day cut of SLA breaches since Daniel specifically asked about time patterns and I have a slot free for it. Headline SLA numbers from report 3 (37.7% first-response breach, 20.3% resolution breach, urgent tickets breaching resolution over half the time) are worth noting to Mia/Daniel now even before drill-down.
The engineer · 1 dry run on the empty schema, then the submission
WITH base AS ( SELECT t.id AS ticket_id, t.created_at, t.first_response_at, cd.day_name, cd.iso_day_of_week, EXTRACT(HOUR FROM t.created_at)::int AS hr FROM tickets t JOIN calendar_days cd ON cd.calendar_date = CAST(t.created_at AS date) WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.sla_policy_id IS NOT NULL ), bucketed AS ( SELECT ticket_id, created_at, first_response_at, day_name, iso_day_of_week, CASE WHEN hr >= 0 AND hr < 6 THEN 'Night (00-06)' WHEN hr >= 6 AND hr < 12 THEN 'Morning (06-12)' WHEN hr >= 12 AND hr < 18 THEN 'Afternoon (12-18)' ELSE 'Evening (18-24)' END AS time_of_day_bucket, CASE WHEN hr >= 0 AND hr < 6 THEN 1 WHEN hr >= 6 AND hr < 12 THEN 2 WHEN hr >= 12 AND hr < 18 THEN 3 ELSE 4 END AS bucket_order FROM base ), breaches AS ( SELECT ticket_id, MAX(CASE WHEN breach_kind = 'first_response' THEN 1 ELSE 0 END) AS fr_breach, MAX(CASE WHEN breach_kind = 'resolution' THEN 1 ELSE 0 END) AS res_breach FROM sla_breaches GROUP BY ticket_id ), joined AS ( SELECT b.*, COALESCE(br.fr_breach, 0) AS fr_breach, COALESCE(br.res_breach, 0) AS res_breach FROM bucketed b LEFT JOIN breaches br ON br.ticket_id = b.ticket_id ) SELECT day_name AS day_of_week, time_of_day_bucket, COUNT(*) AS ticket_count, ROUND(100.0 * SUM(fr_breach) / COUNT(*), 1) AS first_response_breach_rate, ROUND(100.0 * SUM(res_breach) / COUNT(*), 1) AS resolution_breach_rate, ROUND(AVG(CASE WHEN first_response_at IS NOT NULL THEN EXTRACT(EPOCH FROM (first_response_at - created_at)) / 60.0 END)) AS avg_first_response_minutes FROM joined GROUP BY day_name, iso_day_of_week, time_of_day_bucket, bucket_order ORDER BY iso_day_of_week, bucket_order
“Query compiles cleanly with the expected 6 columns. Submitting now.”
“Submitted. One thing to flag for Ben: sla_breaches.breach_kind isn't an enumerated column in the catalog, so I assumed its values are 'first_response' and 'resolution' to match the ticket SLA timestamp naming — I noted this assumption in the submit note in case the actual codes differ and the breach rates need a quick correction.”
As Ben Carter saw it, from the small dataset the agents worked on
day_of_week | time_of_day_bucket | ticket_count | first_response_breach_rate | resolution_breach_rate | avg_first_response_minutes ------------+--------------------+--------------+----------------------------+------------------------+--------------------------- Monday | Night (00-06) | 75 | 30.7 | 20.0 | 1180.0 Monday | Morning (06-12) | 93 | 41.9 | 17.2 | 1381.0 Monday | Afternoon (12-18) | 115 | 33.9 | 14.8 | 1062.0 Monday | Evening (18-24) | 95 | 35.8 | 18.9 | 1524.0 Tuesday | Night (00-06) | 75 | 32.0 | 16.0 | 1414.0 Tuesday | Morning (06-12) | 87 | 36.8 | 21.8 | 1149.0 Tuesday | Afternoon (12-18) | 95 | 38.9 | 16.8 | 1226.0 Tuesday | Evening (18-24) | 95 | 36.8 | 28.4 | 1305.0 Wednesday | Night (00-06) | 81 | 38.3 | 22.2 | 1085.0 Wednesday | Morning (06-12) | 101 | 35.6 | 12.9 | 1178.0 Wednesday | Afternoon (12-18) | 125 | 39.2 | 16.8 | 1328.0 Wednesday | Evening (18-24) | 71 | 38.0 | 15.5 | 1888.0 Thursday | Night (00-06) | 71 | 35.2 | 26.8 | 2155.0 Thursday | Morning (06-12) | 76 | 35.5 | 19.7 | 1610.0 Thursday | Afternoon (12-18) | 75 | 41.3 | 26.7 | 1683.0 Thursday | Evening (18-24) | 75 | 36.0 | 17.3 | 1406.0 Friday | Night (00-06) | 43 | 48.8 | 27.9 | 1797.0 Friday | Morning (06-12) | 68 | 33.8 | 22.1 | 1327.0 Friday | Afternoon (12-18) | 86 | 37.2 | 16.3 | 1507.0 Friday | Evening (18-24) | 75 | 33.3 | 22.7 | 1868.0 Saturday | Night (00-06) | 61 | 45.9 | 29.5 | 2124.0 Saturday | Morning (06-12) | 54 | 37.0 | 31.5 | 1718.0 Saturday | Afternoon (12-18) | 84 | 35.7 | 19.0 | 2088.0 Saturday | Evening (18-24) | 91 | 42.9 | 25.3 | 1954.0 Sunday | Night (00-06) | 81 | 37.0 | 22.2 | 1804.0 Sunday | Morning (06-12) | 75 | 38.7 | 18.7 | 996.0 Sunday | Afternoon (12-18) | 104 | 47.1 | 22.1 | 1734.0 Sunday | Evening (18-24) | 93 | 35.5 | 14.0 | 1309.0 (28 rows)
The memo and the queries that followed; links found by title, so inferred
Later queries that build on this report:
It builds on:
Changed only where the benchmark needs the same result on every system
WITH base AS ( SELECT t.id AS ticket_id, t.created_at, t.first_response_at, cd.day_name, cd.iso_day_of_week, EXTRACT(HOUR FROM t.created_at)::int AS hr t.created_at AS created_at, t.first_response_at AS first_response_at, cd.day_name AS day_name, cd.iso_day_of_week AS iso_day_of_week, CAST(EXTRACT(HOUR FROM t.created_at) AS int) AS hr FROM tickets t JOIN calendar_days cd ON cd.calendar_date = CAST(t.created_at AS date) WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.sla_policy_id IS NOT NULL),bucketed AS ( SELECT ticket_id, created_at, first_response_at, day_name, iso_day_of_week, CASE WHEN hr >= 0 AND hr < 6 THEN 'Night (00-06)' WHEN hr >= 6 AND hr < 12 THEN 'Morning (06-12)' WHEN hr >= 12 AND hr < 18 THEN 'Afternoon (12-18)' ELSE 'Evening (18-24)' END AS time_of_day_bucket, CASE WHEN hr >= 0 AND hr < 6 THEN 1 WHEN hr >= 6 AND hr < 12 THEN 2 WHEN hr >= 12 AND hr < 18 THEN 3 ELSE 4 END AS bucket_order FROM base),breaches AS ( SELECT ticket_id, MAX(CASE WHEN breach_kind = 'first_response' THEN 1 ELSE 0 END) AS fr_breach, MAX(CASE WHEN breach_kind = 'resolution' THEN 1 ELSE 0 END) AS res_breach FROM sla_breaches GROUP BY ticket_id),joined AS ( SELECT b.*, COALESCE(br.fr_breach, 0) AS fr_breach, COALESCE(br.res_breach, 0) AS res_breach FROM bucketed b LEFT JOIN breaches br ON br.ticket_id = b.ticket_id)SELECT day_name AS day_of_week, time_of_day_bucket, COUNT(*) AS ticket_count, ROUND(100.0 * SUM(fr_breach) / COUNT(*), 1) AS first_response_breach_rate, ROUND(100.0 * SUM(res_breach) / COUNT(*), 1) AS resolution_breach_rate, ROUND(AVG(CASE WHEN first_response_at IS NOT NULL THEN EXTRACT(EPOCH FROM (first_response_at - created_at)) / 60.0 END)) AS avg_first_response_minutes 100.0 * SUM(fr_breach) / COUNT(*) AS first_response_breach_rate, 100.0 * SUM(res_breach) / COUNT(*) AS resolution_breach_rate, AVG(CASE WHEN first_response_at IS NOT NULL THEN {{seconds_between(created_at, first_response_at)}} / 60.0 END) AS avg_first_response_minutesFROM joinedGROUP BY day_name, iso_day_of_week, time_of_day_bucket, bucket_orderORDER BY iso_day_of_week, bucket_orderORDER BY iso_day_of_week NULLS LAST, bucket_order NULLS LAST, day_name NULLS LAST
WITH base AS ( SELECT t.id AS ticket_id, t.created_at AS created_at, t.first_response_at AS first_response_at, cd.day_name AS day_name, cd.iso_day_of_week AS iso_day_of_week, CAST(EXTRACT(HOUR FROM t.created_at) AS int) AS hr FROM tickets t JOIN calendar_days cd ON cd.calendar_date = CAST(t.created_at AS date) WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.sla_policy_id IS NOT NULL ), bucketed AS ( SELECT ticket_id, created_at, first_response_at, day_name, iso_day_of_week, CASE WHEN hr >= 0 AND hr < 6 THEN 'Night (00-06)' WHEN hr >= 6 AND hr < 12 THEN 'Morning (06-12)' WHEN hr >= 12 AND hr < 18 THEN 'Afternoon (12-18)' ELSE 'Evening (18-24)' END AS time_of_day_bucket, CASE WHEN hr >= 0 AND hr < 6 THEN 1 WHEN hr >= 6 AND hr < 12 THEN 2 WHEN hr >= 12 AND hr < 18 THEN 3 ELSE 4 END AS bucket_order FROM base ), breaches AS ( SELECT ticket_id, MAX(CASE WHEN breach_kind = 'first_response' THEN 1 ELSE 0 END) AS fr_breach, MAX(CASE WHEN breach_kind = 'resolution' THEN 1 ELSE 0 END) AS res_breach FROM sla_breaches GROUP BY ticket_id ), joined AS ( SELECT b.*, COALESCE(br.fr_breach, 0) AS fr_breach, COALESCE(br.res_breach, 0) AS res_breach FROM bucketed b LEFT JOIN breaches br ON br.ticket_id = b.ticket_id ) SELECT day_name AS day_of_week, time_of_day_bucket, COUNT(*) AS ticket_count, 100.0 * SUM(fr_breach) / COUNT(*) AS first_response_breach_rate, 100.0 * SUM(res_breach) / COUNT(*) AS resolution_breach_rate, AVG(CASE WHEN first_response_at IS NOT NULL THEN CAST(EXTRACT(EPOCH FROM (first_response_at - created_at)) AS NUMERIC(18,6)) / 60.0 END) AS avg_first_response_minutes FROM joined GROUP BY day_name, iso_day_of_week, time_of_day_bucket, bucket_order ORDER BY iso_day_of_week NULLS LAST, bucket_order NULLS LAST, day_name NULLS LAST
WITH base AS ( SELECT t.id AS ticket_id, t.created_at AS created_at, t.first_response_at AS first_response_at, cd.day_name AS day_name, cd.iso_day_of_week AS iso_day_of_week, CAST(EXTRACT(HOUR FROM t.created_at) AS int) AS hr FROM tickets t JOIN calendar_days cd ON cd.calendar_date = CAST(t.created_at AS date) WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.sla_policy_id IS NOT NULL ), bucketed AS ( SELECT ticket_id, created_at, first_response_at, day_name, iso_day_of_week, CASE WHEN hr >= 0 AND hr < 6 THEN 'Night (00-06)' WHEN hr >= 6 AND hr < 12 THEN 'Morning (06-12)' WHEN hr >= 12 AND hr < 18 THEN 'Afternoon (12-18)' ELSE 'Evening (18-24)' END AS time_of_day_bucket, CASE WHEN hr >= 0 AND hr < 6 THEN 1 WHEN hr >= 6 AND hr < 12 THEN 2 WHEN hr >= 12 AND hr < 18 THEN 3 ELSE 4 END AS bucket_order FROM base ), breaches AS ( SELECT ticket_id, MAX(CASE WHEN breach_kind = 'first_response' THEN 1 ELSE 0 END) AS fr_breach, MAX(CASE WHEN breach_kind = 'resolution' THEN 1 ELSE 0 END) AS res_breach FROM sla_breaches GROUP BY ticket_id ), joined AS ( SELECT b.*, COALESCE(br.fr_breach, 0) AS fr_breach, COALESCE(br.res_breach, 0) AS res_breach FROM bucketed b LEFT JOIN breaches br ON br.ticket_id = b.ticket_id ) SELECT day_name AS day_of_week, time_of_day_bucket, COUNT(*) AS ticket_count, 100.0 * SUM(fr_breach) / COUNT(*) AS first_response_breach_rate, 100.0 * SUM(res_breach) / COUNT(*) AS resolution_breach_rate, AVG(CASE WHEN first_response_at IS NOT NULL THEN CAST(divideDecimal(toDecimal128(dateDiff('microsecond', created_at, first_response_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 60.0 END) AS avg_first_response_minutes FROM joined GROUP BY day_name, iso_day_of_week, time_of_day_bucket, bucket_order ORDER BY iso_day_of_week NULLS LAST, bucket_order NULLS LAST, day_name 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 Ben Carter (analyst for the Support Operations Manager): Title: SLA breach rates by day of week and time of day Question: Is there a time-of-day or day-of-week pattern to when we breach first-response and resolution SLA targets? Grain: one row per combination of ticket creation day-of-week (Monday..Sunday) and time-of-day bucket (Night 00-06, Morning 06-12, Afternoon 12-18, Evening 18-24, all in UTC) Columns: - day_of_week - time_of_day_bucket - ticket_count (created 2025, sla_policy_id not null) - first_response_breach_rate - resolution_breach_rate - avg_first_response_minutes Details: Window: tickets.created_at between 2025-01-01 and 2025-12-31, restricted to tickets with sla_policy_id not null. Derive day-of-week from tickets.created_at (use calendar_days.day_name joined on the date, or extract directly) and a 6-hour UTC time bucket from the created_at time component. Source tables: tickets, sla_breaches, calendar_days. 28 rows total (7 days x 4 buckets); order by day_of_week (Monday first) then time_of_day_bucket in chronological order (Night, Morning, Afternoon, Evening). Round rates to 1 decimal percent, minutes to whole numbers. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runWITH base AS ( SELECT t.id AS ticket_id, t.created_at, t.first_response_at, cd.day_name, cd.iso_day_of_week, EXTRACT(HOUR FROM t.created_at)::int AS hr FROM tickets t JOIN calendar_days cd ON cd.calendar_date = CAST(t.created_at AS date) WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.sla_policy_id IS NOT NULL ), bucketed AS ( SELECT ticket_id, created_at, first_response_at, day_name, iso_day_of_week, CASE WHEN hr >= 0 AND hr < 6 THEN 'Night (00-06)' WHEN hr >= 6 AND hr < 12 THEN 'Morning (06-12)' WHEN hr >= 12 AND hr < 18 THEN 'Afternoon (12-18)' ELSE 'Evening (18-24)' END AS time_of_day_bucket, CASE WHEN hr >= 0 AND hr < 6 THEN 1 WHEN hr >= 6 AND hr < 12 THEN 2 WHEN hr >= 12 AND hr < 18 THEN 3 ELSE 4 END AS bucket_order FROM base ), breaches AS ( SELECT ticket_id, MAX(CASE WHEN breach_kind = 'first_response' THEN 1 ELSE 0 END) AS fr_breach, MAX(CASE WHEN breach_kind = 'resolution' THEN 1 ELSE 0 END) AS res_breach FROM sla_breaches GROUP BY ticket_id ), joined AS ( SELECT b.*, COALESCE(br.fr_breach, 0) AS fr_breach, COALESCE(br.res_breach, 0) AS res_breach FROM bucketed b LEFT JOIN breaches br ON br.ticket_id = b.ticket_id ) SELECT day_name AS day_of_week, time_of_day_bucket, COUNT(*) AS ticket_count, ROUND(100.0 * SUM(fr_breach) / COUNT(*), 1) AS first_response_breach_rate, ROUND(100.0 * SUM(res_breach) / COUNT(*), 1) AS resolution_breach_rate, ROUND(AVG(CASE WHEN first_response_at IS NOT NULL THEN EXTRACT(EPOCH FROM (first_response_at - created_at)) / 60.0 END)) AS avg_first_response_minutes FROM joined GROUP BY day_name, iso_day_of_week, time_of_day_bucket, bucket_order ORDER BY iso_day_of_week, bucket_order
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: day_of_week, time_of_day_bucket, ticket_count, first_response_breach_rate, resolution_breach_rate, avg_first_response_minutes
submitWITH base AS ( SELECT t.id AS ticket_id, t.created_at, t.first_response_at, cd.day_name, cd.iso_day_of_week, EXTRACT(HOUR FROM t.created_at)::int AS hr FROM tickets t JOIN calendar_days cd ON cd.calendar_date = CAST(t.created_at AS date) WHERE t.created_at >= DATE '2025-01-01' AND t.created_at < DATE '2026-01-01' AND t.sla_policy_id IS NOT NULL ), bucketed AS ( SELECT ticket_id, created_at, first_response_at, day_name, iso_day_of_week, CASE WHEN hr >= 0 AND hr < 6 THEN 'Night (00-06)' WHEN hr >= 6 AND hr < 12 THEN 'Morning (06-12)' WHEN hr >= 12 AND hr < 18 THEN 'Afternoon (12-18)' ELSE 'Evening (18-24)' END AS time_of_day_bucket, CASE WHEN hr >= 0 AND hr < 6 THEN 1 WHEN hr >= 6 AND hr < 12 THEN 2 WHEN hr >= 12 AND hr < 18 THEN 3 ELSE 4 END AS bucket_order FROM base ), breaches AS ( SELECT ticket_id, MAX(CASE WHEN breach_kind = 'first_response' THEN 1 ELSE 0 END) AS fr_breach, MAX(CASE WHEN breach_kind = 'resolution' THEN 1 ELSE 0 END) AS res_breach FROM sla_breaches GROUP BY ticket_id ), joined AS ( SELECT b.*, COALESCE(br.fr_breach, 0) AS fr_breach, COALESCE(br.res_breach, 0) AS res_breach FROM bucketed b LEFT JOIN breaches br ON br.ticket_id = b.ticket_id ) SELECT day_name AS day_of_week, time_of_day_bucket, COUNT(*) AS ticket_count, ROUND(100.0 * SUM(fr_breach) / COUNT(*), 1) AS first_response_breach_rate, ROUND(100.0 * SUM(res_breach) / COUNT(*), 1) AS resolution_breach_rate, ROUND(AVG(CASE WHEN first_response_at IS NOT NULL THEN EXTRACT(EPOCH FROM (first_response_at - created_at)) / 60.0 END)) AS avg_first_response_minutes FROM joined GROUP BY day_name, iso_day_of_week, time_of_day_bucket, bucket_order ORDER BY iso_day_of_week, bucket_order
Submitted to tonight's batch. Your work on this request is done.