This section covers the data provided for the analysis — schema definitions, period labels, row counts, and data quality checks. It establishes the analytical foundation before any segment-level analysis begins.
Data Assets
File
Level
Description
ops_cases.csv
Case
One row per case.
ops_reviews.csv
Review / touch
One row per review action on a case. A case can have multiple reviews.
The dataset covers Email-channel cases only, from January to June 2023 (Weeks 1–26), across Internal and Vendor handler teams.
Period Definitions
The period column in ops_cases encodes three analytical windows that are used throughout this book:
Period
Weeks
What was live
Baseline
1–10
No changes
Self-serve Only
11–13
Automation live; routing unchanged
Both Changes
14–26
Both automation and skill-based routing live
This three-period structure is critical: it allows the effect of each change to be isolated rather than treating the post-Week 11 period as a single block.
Code
import pandas as pdimport matplotlib.pyplot as pltimport warningswarnings.filterwarnings("ignore")# Load datacases = pd.read_csv("ops_cases.csv", parse_dates=["created_at", "resolved_at", "due_at"])reviews = pd.read_csv("ops_reviews.csv", parse_dates=["intended_start_at", "actual_start_at", "end_at"])# Enforce period ordering throughout the bookperiod_order = ["Baseline", "Self-serve Only", "Both Changes"]cases["period"] = pd.Categorical(cases["period"], categories=period_order, ordered=True)print(f"ops_cases: {cases.shape[0]:,} rows × {cases.shape[1]} columns")print(f"ops_reviews: {reviews.shape[0]:,} rows × {reviews.shape[1]} columns")
# Row counts by periodprint("Cases by period:")print(cases.groupby("period")["case_id"].count())print()print("Cases by handler type:")print(cases.groupby("initial_handler_type")["case_id"].count())print()print("Cases by queue:")print(cases.groupby("queue_name")["case_id"].count())
Cases by period:
period
Baseline 5750
Self-serve Only 1342
Both Changes 5749
Name: case_id, dtype: int64
Cases by handler type:
initial_handler_type
Internal 7699
Vendor 5142
Name: case_id, dtype: int64
Cases by queue:
queue_name
Basic_Support 3769
Complex_Cases 3679
Technical_Support 5393
Name: case_id, dtype: int64
Code
# Data quality checksprint("=== Missing values — ops_cases ===")print(cases.isnull().sum()[cases.isnull().sum() >0])print()print("=== Missing values — ops_reviews ===")print(reviews.isnull().sum()[reviews.isnull().sum() >0])print()# Check for resolution_hours anomaliesnegative_res = (cases["resolution_hours"] <0).sum()print(f"Negative resolution_hours: {negative_res}")# Check review orderingmax_reviews = cases["num_reviews"].max()print(f"Max reviews on a single case: {max_reviews}")print(f"Cases with >3 reviews: {(cases['num_reviews'] >3).sum()}")
=== Missing values — ops_cases ===
initial_vendor_name 7699
dtype: int64
=== Missing values — ops_reviews ===
vendor_name 10992
dtype: int64
Negative resolution_hours: 0
Max reviews on a single case: 4
Cases with >3 reviews: 167
Code
# Review categorical variables with more than one options - final outcome and channel only have one optionprint("Queue Name Options:", cases["queue_name"].unique().tolist())print("Initial Handler Type:", cases["initial_handler_type"].unique().tolist())print("Initial Vendor Name Options:", cases["initial_vendor_name"].unique().tolist(),)
Queue Name Options: ['Basic_Support', 'Technical_Support', 'Complex_Cases']
Initial Handler Type: ['Internal', 'Vendor']
Initial Vendor Name Options: [nan, 'VendorB', 'VendorC', 'VendorA']
Code
# Found an overlap of week_number 10, 13 between baseline and self-serve only for week 13print("Baseline Weeks:", cases[cases["period"] =="Baseline"]["week_number"].unique().tolist(),)print("Self-Serve Weeks:", cases[cases["period"] =="Self-serve Only"]["week_number"].unique().tolist(),)print("Both Changes Weeks:", cases[cases["period"] =="Both Changes"]["week_number"].unique().tolist(),)
The notebooks leverage multiple new metrics that are created.
Average Speed of Answer (ASA) - Average time is takes an agent to get started on the case from when the case lands in their queue. Calculated as the difference between the actual_start_at and intended_start_at at a Review Level.
First Time Resolution (FTR) - It is the ratio of cases out of all cases where the case was resolved in it’s first review. It is calculated at case level using num_reviews = 1 and at a review level with review_order=1 and outcome='Completed
They are also explained in the deck and in the notebooks when they are used. Any other measure created is calculated in the notebook itself.