This section formalises the causal argument with statistical tests. The analysis uses vendor teams as a natural control group for the skill-based routing change — vendor teams were subject to the same self-serve automation and the same case mix shift, but were not subject to skill-based routing. Any difference in SLA trajectory between internal and vendor teams after Week 14 is therefore attributable to routing.
Test design: - Chi-square test — confirms internal and vendor SLA distributions were statistically equivalent at baseline and significantly different post-routing - Difference-in-Differences (DiD) — estimates the causal effect of routing by comparing internal vs vendor SLA change from baseline to both-changes period
Assumption: Parallel trends — in the absence of routing, internal and vendor SLA would have followed similar trajectories. This is supported by the baseline period where both groups tracked within 1–2pp of each other, and by the self-serve only period where both groups declined similarly.
Limitation: VendorA showed an independent performance dip around Week 14. This may slightly understate the true routing effect since the vendor control group is imperfect.
Interpretation: We fail to reject H₀. There is no statistically significant difference in SLA pass rates between Internal and Vendor teams during the baseline period. This is a critical validation — it confirms the two groups were operationally comparable before any intervention, supporting the use of vendor teams as a control group in the DiD analysis that follows.
Period B — Both Changes
\(H_{0}\): The proportion of cases resolved within SLA is the same for Internal and Vendor handler types during the both-changes period.
P(SLA pass | Internal, Both Changes) = P(SLA pass | Vendor, Both Changes)
\(H_{1}\): The proportions differ.
P(SLA pass | Internal, Both Changes) ≠ P(SLA pass | Vendor, Both Changes)
Significance level: α = 0.05
Result: χ²(1) = 81.556, p < 0.001
Interpretation: We reject H₀. There is a significant difference in SLA rates between Internal and Vendor teams after both changes are live.
Groups that were statistically equivalent at baseline have diverged sharply. Since the only change that affected Internal teams specifically was skill-based routing, this divergence is attributable to the routing change.
Assumptions and Validity
Independence of observations - each case is a unique event.
Expected cell counts ≥ 5 - satisfied given sample sizes of thousands per cell.
Mutual exclusivity - each case belongs to exactly one handler type and one SLA outcome.
Code
for period in ["Baseline", "Both Changes"]: subset = cases[cases["period"] == period] ct = pd.crosstab(subset["initial_handler_type"], subset["resolved_within_sla"]) chi2, p, dof, expected = chi2_contingency(ct)print(f"\n{'='*40}")print(f"Period: {period}")print(ct) sla_rates = ct.div(ct.sum(axis=1), axis=0)[1].rename("SLA rate")print(sla_rates.round(3))print(f"Chi2: {chi2:.3f}, p-value: {p:.4f}")if p >0.05:print("No significant difference between groups (validates control group)")else:print("Significant difference detected")
Baseline Period SLA Both Changes Period SLA Change
Internal 83.7% 65.7% -18.0%
Vendor 82.2% 76.9% -5.4%
DiD estimate (routing effect): -12.7%
Code
# Contribution of Internal case SLA declineinternal_prop = ( cases[cases["initial_handler_type"] =="Internal"].shape[0] / cases.shape[0])print(f"Contribution of Internal Case SLA decline: {did_estimate*internal_prop:+.2%}")
Contribution of Internal Case SLA decline: -7.61%
Interpretation: After accounting for the -5.4% decline experienced by vendor teams (background + mix shift effect), skill-based routing is estimated to have caused an additional 12.7% decline in internal team SLA.
β0 = baseline SLA for vendor (control) in pre-period
β1 = pre-existing gap between internal and vendor (should be near zero)
β2 = time trend affecting everyone (vendor’s change from pre to post)
β3 = the DiD estimate — the additional effect on internal teams beyond the background trend due to the treatment
Code
# Restrict to Baseline and Both Changes onlydid_data = cases[cases["period"].isin(["Baseline", "Both Changes"])].copy()# Binary indicators for eaach groupdid_data["treated"] = (did_data["initial_handler_type"] =="Internal").astype(int)did_data["post"] = (did_data["period"] =="Both Changes").astype(int)did_data["treated_post"] = did_data["treated"] * did_data["post"]# Basic DiD — Linear Probability Model (LPM)# LPM is standard for DiD with binary outcomes — coefficients are directly interpretable as pp changesmodel1 = smf.ols("resolved_within_sla ~ treated + post + treated_post", data=did_data)result1 = model1.fit(cov_type="HC3") # HC3 = heteroscedasticity-robust standard errorsprint("=== Model 1: Basic DiD ===")print(result1.summary2().tables[1].round(4))print(f"\nDiD estimate: {result1.params['treated_post']:.4f} ({result1.params['treated_post']*100:.2f}pp)")print(f"95% CI: [{result1.conf_int().loc['treated_post', 0]*100:.2f}pp, "f"{result1.conf_int().loc['treated_post', 1]*100:.2f}pp]")print(f"p-value: {result1.pvalues['treated_post']}")
=== Model 1: Basic DiD ===
Coef. Std.Err. z P>|z| [0.025 0.975]
Intercept 0.8225 0.0079 104.2779 0.00 0.8070 0.8379
treated 0.0149 0.0101 1.4757 0.14 -0.0049 0.0347
post -0.0536 0.0119 -4.5160 0.00 -0.0768 -0.0303
treated_post -0.1269 0.0157 -8.0983 0.00 -0.1576 -0.0962
DiD estimate: -0.1269 (-12.69pp)
95% CI: [-15.76pp, -9.62pp]
p-value: 5.574586632611151e-16
Code
# Add queue_name as a covariatemodel2 = smf.ols("resolved_within_sla ~ treated + post + treated_post + C(queue_name)", data=did_data)result2 = model2.fit(cov_type="HC3")print("\n=== Model 2: DiD with Queue Controls ===")print(result2.summary2().tables[1].round(4))print(f"\nDiD estimate (controlled): {result2.params['treated_post']:.4f} "f"({result2.params['treated_post']*100:.2f}pp)")print(f"95% CI: [{result2.conf_int().loc['treated_post', 0]*100:.2f}pp, "f"{result2.conf_int().loc['treated_post', 1]*100:.2f}pp]")
Baseline chi-square (p = 0.147): Internal and vendor SLA were statistically equivalent before any intervention. This validates the parallel trends assumption required for a valid DiD estimate.
Both Changes chi-square (p < 0.001): After both changes are live, the two groups are statistically significantly different. Groups that were identical at baseline have diverged — and the only change that affected internal teams specifically was skill-based routing.
DiD estimate: −12.7pp. Internal SLA declined 18.0pp from baseline to the both-changes period. Vendor SLA declined 5.4pp over the same window, capturing the background effect (mix shift + general difficulty of remaining caseload). Stripping that out, skill-based routing is estimated to have caused an additional −12.7 percentage point decline in internal SLA.
Limitation: The vendor control group is imperfect. VendorA showed an independent performance dip around Week 14 unrelated to routing, which may slightly understate the true routing effect. The true causal estimate may be larger than −12.7pp.
Self- Serve Changes Assessment
“How much of the SLA decline after Week 11 can be explained purely by the change in case complexity mix caused by self-serve deflection?”
Code
# Isolate the data for the self serve period - not considering the routing changes periodcases["period_selfserve"] = cases["week_number"].apply(lambda x: "pre"if x <11else"post")ss_data = cases[cases["week_number"] <14]
Proportions z test
Did the proportion of each queue change significantly after the self-serve period
\(H_{0}\): There is no difference between the two proportions
\(H_{1}\): There is significant difference between the two proportions
Code
# Run the prortions z testtotal_pre =len(ss_data[ss_data["period_selfserve"] =="pre"])total_post =len(ss_data[ss_data["period_selfserve"] =="post"])results = []for queue in cases["queue_name"].unique(): count_pre =len( ss_data[ (ss_data["period_selfserve"] =="pre") & (ss_data["queue_name"] == queue) ] ) count_post =len( ss_data[ (ss_data["period_selfserve"] =="post") & (ss_data["queue_name"] == queue) ] )# Test: did this queue's share of total volume change? stat, p = proportions_ztest( count=[count_pre, count_post], nobs=[total_pre, total_post] ) results.append( {"queue": queue,"share_pre": round(count_pre / total_pre, 3),"share_post": round(count_post / total_post, 3),"share_delta": round( (count_post / total_post) - (count_pre / total_pre), 3 ),"z_stat": round(stat, 3),"p_value": round(p, 4),"significant": p <0.05, } )pd.DataFrame(results).sort_values("p_value")
queue
share_pre
share_post
share_delta
z_stat
p_value
significant
0
Basic_Support
0.405
0.189
-0.216
14.747
0.0000
True
2
Complex_Cases
0.198
0.369
0.171
-13.392
0.0000
True
1
Technical_Support
0.397
0.442
0.045
-2.991
0.0028
True
Quantify mechanical SLA impact from mix shift
If agent performance within each complexity bucket stayed constant, how much SLA decline would occur purely because the mix changed? Using counterfactuals
# Counterfactual SLAprint("Expected SLA if only the workload mix changed.: ", (np.dot(sla_table["sla_rate"], sla_table["proportion"]).round(3)),)
Expected SLA if only the workload mix changed.: 0.805
Interpret the Results
Actual Baseline SLA: 83.1%
Expected SLA from mix shift: 80.5%
Actual observed Weeks 11–13 SLA: 78.8%
Results
2.6pp decline explained by complexity mix
Additional 1.7pp decline likely effects of solving harder cases and time spent increasing
Code
# Run the prortions z test to see if the SLA decline is significant or not# Split periodspre = cases[cases["period"] =="Baseline"]post = cases[cases["period"] =="Self-serve Only"]# Number of SLA successessla_pre = pre["resolved_within_sla"].sum()sla_post = post["resolved_within_sla"].sum()# Total observationsn_pre =len(pre)n_post =len(post)# Two proportion z-teststat, p = proportions_ztest(count=[sla_pre, sla_post], nobs=[n_pre, n_post])print("Pre SLA:", round(sla_pre / n_pre, 3))print("Post SLA:", round(sla_post / n_post, 3))print("Delta:", round((sla_post / n_post) - (sla_pre / n_pre), 3))print("Z-stat:", round(stat, 3))print("P-value:", round(p, 5))print("Significant:", p <0.05)
Pre SLA: 0.831
Post SLA: 0.788
Delta: -0.044
Z-stat: 3.775
P-value: 0.00016
Significant: True
The SLA decline following Week 11 is statistically significant and unlikely due to random operational variation.