MLOps course · lesson 12 of 24
- 1What Is MLOps? From Notebooks to Reliable Production
- 2MLOps Principles and Maturity: From Manual to Automated
- 3ML Fundamentals for MLOps: Splits, Leakage, and Metrics
- 4A Reproducible ML Project: Layout, Seeds, and Data Code
- 5Data Engineering Basics for ML: Ingestion, SQL, and Skew
- 6Pipeline Orchestration: Apache Airflow and Kubeflow
- 7Batch vs Streaming: Spark, Kafka, and Flink
- 8Data Versioning and Lineage: DVC and Beyond
- 9Data Validation and Quality in ML Pipelines
- 10Experiment Tracking with MLflow
- 11Reproducible Training Pipelines
- 12Model Evaluation and Validation Gates
- 13Model Registry and Versioning
- 14CI/CD for Machine Learning
- 15Containers and Kubernetes for ML
- 16Infrastructure as Code: Terraform and Ansible
- 17Cloud ML Platforms: AWS, Azure, GCP, and Self-Built
- 18Model Serving Patterns: Batch, Online, and Streaming
- 19Inference Engineering Basics: Latency, Batching, and Cost
- 20Monitoring and Observability for ML Models
- 21Drift Detection and Retraining for ML Models
- 22Explainable AI: LIME, SHAP, and Model Interpretability
- 23Edge AI Deployment: Jetson, TFLite, and Mobile ML
- 24Capstone: An End-to-End MLOps Pipeline
In software delivery, code changes must pass a battery of automated tests before merging to the main branch. Unit tests verify individual functions, integration tests check database queries, and end-to-end suites confirm user flows. If a single assertion fails, continuous integration halts and the release is blocked.
In machine learning, code compilation and unit tests are necessary but insufficient. A newly trained model can compile cleanly, pass every unit test, and output valid probabilities between 0.0 and 1.0, while being objectively worse than the model currently running in production. A retrained customer churn model might achieve a slightly higher overall ROC AUC score across the entire test set, while quietly regressing on the company's highest-spending enterprise clients.
If model promotion depends on an engineer manually eyeballing a confusion matrix on a Friday afternoon, bad models will reach users. Production safety requires an objective, automated evaluation gate.
In this lesson, you will learn how to design automated validation gates that decide whether a candidate model is worthy of replacing the current production champion. You will explore slice-level testing, probability calibration, statistical noise in small validation sets, the role of human review, and implement a fully tested evaluation gate in Python.
The StayWell figures in this lesson (rates, scores, counts and prices) are made up for illustration. They show how to read the numbers, not what real systems achieve.
The limits of single aggregate metrics
Most introductory machine learning courses teach practitioners to evaluate models using a single summary metric: overall accuracy, F1 score, or ROC AUC calculated across the entire test partition.
In production MLOps, aggregate metrics are deceptive.

Consider the StayWell customer churn scenario illustrated above. The active production model (the Champion) achieves an overall ROC AUC of 0.824. A data scientist trains a new Gradient Boosting model (the Challenger) that scores 0.851 on the exact same holdout set, an apparent improvement of nearly three percentage points.
If the team relies solely on the overall metric, the Challenger is immediately promoted. However, when the evaluation dataset is segmented into operational customer slices, a dangerous reality emerges:
- Standard Subscribers (72% of users): The Challenger's AUC jumped from 0.810 to 0.858.
- New Accounts (18% of users): The Challenger improved from 0.795 to 0.812.
- VIP Guests (10% of users, representing 55% of revenue): The Challenger's AUC plummeted from 0.862 down to 0.781, a severe regression of eight percentage points.
Because standard subscribers outnumber VIP guests seven to one, the dramatic improvement on low-value accounts masked the catastrophic degradation on high-value accounts. If deployed, StayWell's retention team would fail to identify churn signals among their most valuable customers, directly harming recurring revenue.
The four layers of an evaluation gate
An automated evaluation gate acts as a quality firewall between model training and model registration. A candidate model must pass four sequential evaluation layers before reaching staging or production:
Layer 1: Absolute minimum performance threshold
Before comparing the candidate against other models, verify that it meets the bare minimum operational threshold required for business utility:
- Rule: Candidate metric must equal or exceed a hard floor (for example,
ROC AUC >= 0.80orPrecision@10% >= 0.65). - Purpose: Protects against training corruption, unseeded data shuffles, or severe pipeline failures where an algorithm failed to converge.
Layer 2: Champion comparison (Non-regression check)
The candidate must demonstrate parity or improvement over the incumbent model currently serving production traffic:
- Rule: Candidate metric must satisfy
Candidate_AUC >= Champion_AUC(or within a defined non-regression tolerance). - Purpose: Prevents deploying retrained models that underperform existing production baselines. In mature setups,
Toleranceis typically set to0.00(meaning the new model must strictly match or beat the champion).
Layer 3: Slice-level degradation boundaries
The dataset is partitioned into critical business slices (such as geographic regions, device categories, subscription tiers, or tenure cohorts):
- Rule: For every critical slice , the candidate metric must satisfy
Candidate_Slice_AUC >= Champion_Slice_AUC(or within the allowable slice degradation limit). - Purpose: Ensures overall gains do not come at the expense of high-value or vulnerable sub-populations.
Layer 4: Probability calibration check
For operational decisions, ranking order alone is not enough; the raw probability values must reflect real-world frequencies. If a churn model outputs a predicted probability of 0.80, eighty out of one hundred customers with that score should actually churn.
The Brier Score measures calibration accuracy by calculating the mean squared difference between predicted probabilities and binary outcomes:
A lower Brier score indicates superior calibration (with 0.0 representing perfect forecasts).
- Rule:
Candidate_Brier <= Max_Allowed_Brier(for example,Brier <= 0.15). - Purpose: Prevents promoting uncalibrated models that output extreme, overconfident probabilities that distort automated retention discounting workflows.
Contrast with LLM evaluation
It is instructive to contrast classical tabular evaluation gates with modern Large Language Model (LLM) evaluations. In our guide on evaluating AI agent systems, evaluation often relies on LLM-as-a-judge rubrics, semantic similarity embeddings, and deterministic tool-call trajectory verifications because natural language outputs lack single ground-truth mathematical labels.
In classical supervised MLOps, evaluation is grounded in objective historical labels. You have exact historical records of whether a StayWell guest renewed or cancelled their reservation. Consequently, classical evaluation gates rely on rigorous statistical hypothesis tests and bounded mathematical tolerances rather than heuristic language scoring.
Statistical noise and sample size limits
A common pitfall in automated gating is treating small metric differences as conclusive evidence of superior predictive power.
If your evaluation holdout set contains only two hundred records and the candidate achieves an accuracy of 82.5% while the champion scores 81.0%, that difference represents exactly three test samples. A three-sample difference is statistically indistinguishable from random noise.
When configuring evaluation gates:
- Enforce minimum sample sizes: Slices containing fewer than fifty positive cases should be flagged as underpowered. Automated gates should evaluate statistical confidence intervals rather than raw point estimates on small cohorts.
- Use bootstrapping for confidence intervals: Compute metric distributions by drawing one thousand bootstrap resamples from the holdout set. Only promote a candidate if its lower confidence bound matches the champion's median performance.
Gate architecture: automation with human oversight
Should a passing evaluation gate immediately trigger automated deployment to live production?
In high-stakes production systems, the recommended pattern is Automated Gating with Human-in-the-Loop Triage.

The gating workflow operates in three branches:
- Hard Failure (Auto-Reject): If the candidate fails Layer 1 (absolute threshold) or Layer 2 (champion parity), the gate rejects the model automatically. An execution summary is posted to Slack, the run is archived, and zero human engineering time is wasted.
- Clean Pass (Auto-Promote to Staging): If the candidate passes all absolute, champion, calibration, and slice checks without warnings, the model is automatically promoted to the Staging environment and begins receiving shadow traffic.
- Amber Borderline (Human Review Required): If the candidate improves overall accuracy by 4% but triggers a minor warning on a non-critical slice (or falls within a tight statistical margin of error), the gate pauses promotion. It creates a review ticket and attaches the candidate's Model Card for evaluation by a lead ML engineer.
A lead engineer inspects the trade-off, checks the business context (such as an ongoing promotional campaign that altered customer behavior), and issues a digital cryptographic approval token to override the gate.
Operational template: the production model card
Every model nominated for production promotion should be documented by an automated Model Card. Originating from research by Mitchell et al., model cards provide a standardized executive and technical summary of model capabilities, limitations, and evaluation slice metrics.
1# StayWell Model Card: Customer Churn Classifier
2# Model Identifier: staywell-churn-classifier:v2.4.0
3# Evaluation Date: 2026-11-05
4# Release Status: CANDIDATE FOR PROMOTION
5
6## 1. Model Overview
7- Architecture: Gradient Boosting Decision Trees (scikit-learn Pipeline)
8- Input Schema: 3 numerical features (tenure, spend, tickets), 1 categorical (contract)
9- Output: Calibrated churn probability [0.0, 1.0] and binary flag at threshold 0.35
10- Training Run: MLflow Run `3a7c89b201f94a8e`
11- Git Commit: `a94f8e12d59a7701bc498263152a4e98f092b771`
12- Dataset Snapshot: `7b9c1d0f8a32b1e459021cfa6b0091ef` (250,000 rows)
13
14## 2. Intended Use and Scope
15- Primary Use: Scoring active subscriber churn probability 30 days prior to renewal
16- Out-of-Scope: Enterprise corporate group bookings (handled by B2B sales team)
17- Operating SLA: P95 inference latency under 5.0 ms in production REST API
18
19## 3. Evaluation Gate Results
20- Test Holdout Size: 50,000 records (Point-in-time cut: 2026-11-01)
21- Overall Performance:
22 - Candidate ROC AUC: 0.851 (Champion Baseline: 0.824 | Delta: +0.027) -> PASSED
23 - Candidate Brier Calibration: 0.091 (Max Allowed: 0.150) -> PASSED
24- Critical Slice Performance:
25 - Standard Subscribers: AUC 0.858 (Champion: 0.810 | Delta: +0.048) -> PASSED
26 - New Accounts (< 3 months): AUC 0.812 (Champion: 0.795 | Delta: +0.017) -> PASSED
27 - VIP Guests (Top 10% Spend): AUC 0.864 (Champion: 0.862 | Delta: +0.002) -> PASSED
28
29## 4. Ethical and Operational Limitations
30- Performance degrades on customers with fewer than 30 days of telemetry history
31- Missing support ticket counts are imputed using median values (0.0)
32
33## 5. Promotion Sign-off
34- Gate Status: PASSED ALL CHECKS
35- Staging Deployment: Approved automatically by CI Gatekeeper
36- Lead MLOps Approval: Lead ML Engineer (Digital Sign-off: 2026-11-05T00:00:00Z)Hands-on implementation: a tested Python evaluation gate
To understand how automated validation gates operate inside CI pipelines, let us build a complete evaluation gate using the Python standard library.
This implementation provides:
- Exact mathematical calculation of ROC AUC using Wilcoxon-Mann-Whitney rank sums
- Brier score calculation for probability calibration
- Declarative
GatePolicydataclass defining acceptance rules - Slice-level evaluation checks over arbitrary sub-population columns
- A
GateReportthat records each check decision
The evaluation gate implementation (evaluation_gate.py)
Here is the complete implementation:
evaluation_gate.py
1"""Objective model evaluation and validation gate for MLOps pipelines.
2
3Evaluates candidate models against production champions, enforcing absolute thresholds,
4champion parity, slice-level sub-population health, and probability calibration.
5"""
6
7from __future__ import annotations
8
9import dataclasses
10import math
11from typing import Any, Dict, List, Optional
12
13
14def compute_roc_auc(y_true: List[int], y_prob: List[float]) -> float:
15 """Calculate ROC AUC using the trapezoidal rule over sorted ranks."""
16 if len(y_true) != len(y_prob):
17 raise ValueError("Lengths of y_true and y_prob must match.")
18
19 n_pos = sum(y_true)
20 n_neg = len(y_true) - n_pos
21 if n_pos == 0 or n_neg == 0:
22 return 0.5
23
24 # Pair and sort descending by predicted probability
25 paired = sorted(zip(y_prob, y_true), key=lambda x: x[0], reverse=True)
26
27 # Rank sum calculation (Wilcoxon-Mann-Whitney statistic)
28 rank_sum_pos = 0.0
29 for rank, (prob, label) in enumerate(reversed(paired), start=1):
30 if label == 1:
31 rank_sum_pos += rank
32
33 u = rank_sum_pos - (n_pos * (n_pos + 1)) / 2.0
34 return u / (n_pos * n_neg)
35
36
37def compute_brier_score(y_true: List[int], y_prob: List[float]) -> float:
38 """Compute mean squared error between probabilities and binary labels (Brier score)."""
39 if not y_true:
40 return 0.0
41 squared_errors = [(p - y) ** 2 for y, p in zip(y_true, y_prob)]
42 return sum(squared_errors) / len(squared_errors)
43
44
45@dataclasses.dataclass(frozen=True)
46class GatePolicy:
47 """Policy rules defining acceptance criteria for a candidate model."""
48 min_absolute_auc: float = 0.80
49 max_champion_auc_drop: float = 0.00 # Candidate must match or beat champion overall
50 max_slice_auc_drop: float = 0.03 # No critical slice may regress by more than 3%
51 max_brier_score: float = 0.15 # Calibration error limit
52 critical_slices: List[str] = dataclasses.field(default_factory=lambda: ["vip"])
53
54
55@dataclasses.dataclass
56class CheckDecision:
57 name: str
58 passed: bool
59 candidate_val: float
60 reference_val: Optional[float]
61 message: str
62
63
64@dataclasses.dataclass
65class GateReport:
66 passed: bool
67 overall_candidate_auc: float
68 overall_champion_auc: Optional[float]
69 brier_score: float
70 decisions: List[CheckDecision]
71 slice_metrics: Dict[str, Dict[str, float]]
72
73
74class EvaluationGate:
75 """Evaluates candidate model predictions against policy and champion baseline."""
76
77 def __init__(self, policy: GatePolicy) -> None:
78 self.policy = policy
79
80 def evaluate(
81 self,
82 y_true: List[int],
83 candidate_probs: List[float],
84 champion_probs: Optional[List[float]] = None,
85 slices: Optional[Dict[str, List[str]]] = None,
86 ) -> GateReport:
87 decisions: List[CheckDecision] = []
88
89 # 1. Overall Candidate AUC
90 cand_auc = compute_roc_auc(y_true, candidate_probs)
91 passed_min = cand_auc >= self.policy.min_absolute_auc
92 decisions.append(
93 CheckDecision(
94 name="min_absolute_auc",
95 passed=passed_min,
96 candidate_val=cand_auc,
97 reference_val=self.policy.min_absolute_auc,
98 message=f"Candidate AUC {cand_auc:.4f} vs threshold {self.policy.min_absolute_auc:.4f}",
99 )
100 )
101
102 # 2. Champion Parity Check
103 champ_auc: Optional[float] = None
104 if champion_probs is not None:
105 champ_auc = compute_roc_auc(y_true, champion_probs)
106 min_allowed = champ_auc - self.policy.max_champion_auc_drop
107 passed_champ = cand_auc >= min_allowed
108 decisions.append(
109 CheckDecision(
110 name="champion_comparison",
111 passed=passed_champ,
112 candidate_val=cand_auc,
113 reference_val=champ_auc,
114 message=f"Candidate AUC {cand_auc:.4f} vs Champion AUC {champ_auc:.4f}",
115 )
116 )
117
118 # 3. Probability Calibration Check (Brier Score)
119 brier = compute_brier_score(y_true, candidate_probs)
120 passed_brier = brier <= self.policy.max_brier_score
121 decisions.append(
122 CheckDecision(
123 name="brier_calibration",
124 passed=passed_brier,
125 candidate_val=brier,
126 reference_val=self.policy.max_brier_score,
127 message=f"Candidate Brier {brier:.4f} vs max allowed {self.policy.max_brier_score:.4f}",
128 )
129 )
130
131 # 4. Slice-Level Evaluation Checks
132 slice_metrics: Dict[str, Dict[str, float]] = {}
133 if slices:
134 for slice_col, slice_tags in slices.items():
135 tag_indices: Dict[str, List[int]] = {}
136 for idx, tag in enumerate(slice_tags):
137 tag_indices.setdefault(tag, []).append(idx)
138
139 for tag, idxs in tag_indices.items():
140 sub_y = [y_true[i] for i in idxs]
141 sub_cand_p = [candidate_probs[i] for i in idxs]
142
143 if sum(sub_y) == 0 or sum(sub_y) == len(sub_y):
144 continue
145
146 sub_cand_auc = compute_roc_auc(sub_y, sub_cand_p)
147 slice_key = f"{slice_col}:{tag}"
148 slice_metrics[slice_key] = {"candidate_auc": sub_cand_auc}
149
150 if champion_probs is not None:
151 sub_champ_p = [champion_probs[i] for i in idxs]
152 sub_champ_auc = compute_roc_auc(sub_y, sub_champ_p)
153 slice_metrics[slice_key]["champion_auc"] = sub_champ_auc
154
155 # Check critical slice degradation
156 if tag in self.policy.critical_slices or slice_col in self.policy.critical_slices:
157 drop = sub_champ_auc - sub_cand_auc
158 passed_slice = drop <= self.policy.max_slice_auc_drop
159 decisions.append(
160 CheckDecision(
161 name=f"slice_check_{slice_key}",
162 passed=passed_slice,
163 candidate_val=sub_cand_auc,
164 reference_val=sub_champ_auc,
165 message=(
166 f"Critical slice {slice_key} candidate AUC {sub_cand_auc:.4f} "
167 f"vs Champion {sub_champ_auc:.4f} (drop: {drop:.4f})"
168 ),
169 )
170 )
171
172 all_passed = all(d.passed for d in decisions)
173 return GateReport(
174 passed=all_passed,
175 overall_candidate_auc=cand_auc,
176 overall_champion_auc=champ_auc,
177 brier_score=brier,
178 decisions=decisions,
179 slice_metrics=slice_metrics,
180 )The unit test suite (evaluation_gate_test.py)
Here is the test suite validating successful candidate promotion, rejection on absolute thresholds, rejection on champion regression, rejection on hidden slice degradation, and rejection on probability miscalibration:
evaluation_gate_test.py
1"""Unit tests for the MLOps evaluation and validation gate."""
2
3import unittest
4from evaluation_gate import EvaluationGate, GatePolicy
5
6
7class TestEvaluationGate(unittest.TestCase):
8
9 def setUp(self):
10 self.policy = GatePolicy(
11 min_absolute_auc=0.80,
12 max_champion_auc_drop=0.00,
13 max_slice_auc_drop=0.03,
14 max_brier_score=0.15,
15 critical_slices=["vip"],
16 )
17 self.gate = EvaluationGate(self.policy)
18
19 # Base labels (10 records: 5 churn, 5 non-churn)
20 self.y_true = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
21
22 # Slices: 4 records in VIP (indices 0, 1 churn; 5, 6 non-churn)
23 # 6 records in Standard (indices 2, 3, 4 churn; 7, 8, 9 non-churn)
24 self.slices = {
25 "tier": ["vip", "vip", "std", "std", "std", "vip", "vip", "std", "std", "std"]
26 }
27
28 # Champion predictions (baseline AUC = 1.0 on this test partition)
29 self.champ_probs = [0.85, 0.80, 0.75, 0.70, 0.65, 0.35, 0.30, 0.25, 0.20, 0.15]
30
31 def test_strong_candidate_passes_gate(self):
32 cand_probs = [0.95, 0.90, 0.88, 0.82, 0.78, 0.15, 0.12, 0.10, 0.08, 0.05]
33 report = self.gate.evaluate(self.y_true, cand_probs, self.champ_probs, self.slices)
34
35 self.assertTrue(report.passed)
36 self.assertGreater(report.overall_candidate_auc, 0.80)
37 self.assertGreaterEqual(report.overall_candidate_auc, report.overall_champion_auc)
38 self.assertLessEqual(report.brier_score, 0.15)
39
40 def test_failing_absolute_auc_rejects_candidate(self):
41 weak_probs = [0.55, 0.45, 0.52, 0.48, 0.50, 0.51, 0.49, 0.53, 0.47, 0.50]
42 report = self.gate.evaluate(self.y_true, weak_probs, self.champ_probs, self.slices)
43
44 self.assertFalse(report.passed)
45 self.assertTrue(any(d.name == "min_absolute_auc" and not d.passed for d in report.decisions))
46
47 def test_failing_champion_comparison_rejects_candidate(self):
48 # Candidate passes min absolute AUC (0.80 >= 0.80), but lower than champion (1.0)
49 cand_probs = [0.90, 0.85, 0.80, 0.42, 0.35, 0.50, 0.45, 0.40, 0.15, 0.10]
50 report = self.gate.evaluate(self.y_true, cand_probs, self.champ_probs, self.slices)
51
52 champ_check = next(d for d in report.decisions if d.name == "champion_comparison")
53 self.assertFalse(champ_check.passed)
54 self.assertFalse(report.passed)
55
56 def test_hidden_slice_regression_rejects_candidate(self):
57 # Candidate has high overall AUC by predicting standard slice perfectly,
58 # but inverts predictions on the critical VIP slice
59 cand_probs = [0.30, 0.35, 0.99, 0.98, 0.95, 0.45, 0.50, 0.05, 0.02, 0.01]
60 report = self.gate.evaluate(self.y_true, cand_probs, self.champ_probs, self.slices)
61
62 self.assertGreaterEqual(report.overall_candidate_auc, 0.80)
63 vip_check = next(d for d in report.decisions if "vip" in d.name)
64 self.assertFalse(vip_check.passed)
65 self.assertFalse(report.passed)
66
67 def test_uncalibrated_probabilities_fail_brier_check(self):
68 # Good ranking order, but severely uncalibrated probabilities
69 uncalibrated_probs = [0.95, 0.94, 0.93, 0.92, 0.91, 0.75, 0.74, 0.73, 0.72, 0.71]
70 report = self.gate.evaluate(self.y_true, uncalibrated_probs, self.champ_probs, self.slices)
71
72 brier_check = next(d for d in report.decisions if d.name == "brier_calibration")
73 self.assertFalse(brier_check.passed)
74 self.assertFalse(report.passed)
75
76
77if __name__ == "__main__":
78 unittest.main()Save both files in the same folder and run the tests from that folder:
1python3 -m unittest discover -p '*_test.py'By embedding this evaluation logic directly into your continuous integration workflow, every candidate model is subjected to rigorous, objective gating before deployment.
Mistakes beginners make
1. Promoting models based on aggregate metrics alone
Relying exclusively on global test accuracy or whole-dataset ROC AUC is the most common failure in production ML. An overall gain can conceal severe regressions in critical business segments, such as newly registered users or high-value VIP accounts.
Fix: Incorporate mandatory slice-level evaluation checks into your automated gate. Define critical business cohorts and configure the gate to fail if performance on any key slice drops by more than an allowable tolerance.
2. Evaluating models on dynamic or drifting test sets
Comparing a candidate trained today against last month's champion on a test set that changed between runs invalidates the comparison. If the test set received new records, you cannot determine whether metric changes stem from model improvements or dataset shifts.
Fix: Maintain a versioned, immutable Golden Test Set locked with cryptographic content hashes (via DVC or object storage). Always evaluate both champion and challenger on the identical test partition.
3. Ignoring probability calibration
Developers often assume that high AUC implies high-quality probabilities. However, complex tree ensembles and neural networks frequently output overconfident probabilities that are poorly calibrated, distorting downstream business threshold logic and customer discounting rules.
Fix: Include calibration metrics (such as the Brier score or expected calibration error) in your evaluation gate. Require candidate models to satisfy calibration error ceilings before allowing production release.
4. Over-reacting to small sample fluctuations
When evaluating models on small customer segments (such as fewer than one hundred test examples), minor metric shifts (like a 1.5% drop) often reflect statistical sample variance rather than true algorithmic decay.
Fix: Require minimum sample size thresholds before enforcing automated slice gating. For smaller cohorts, calculate bootstrap confidence intervals to confirm whether differences are statistically meaningful before rejecting candidates.
Recap and try it yourself
In this lesson, you explored how automated evaluation gates decide whether candidate models may replace production champions:
- Single aggregate metrics hide critical regressions in high-value business sub-populations.
- An evaluation gate enforces four layers: absolute minimum floors, champion non-regression parity, slice-level degradation limits, and Brier score calibration checks.
- Classical supervised MLOps evaluates models against objective historical ground-truth labels, contrasting with heuristic LLM-as-a-judge patterns.
- Automated gates triage models into three outcomes: auto-reject for hard failures, auto-promote to staging for clean passes, and human review for borderline trade-offs.
- A standardized Model Card provides transparent documentation of model capabilities, limitations, and slice metrics.
Try it yourself
To practice implementing evaluation gates on your local machine:
- Run the evaluation gate test suite: Copy
evaluation_gate.pyandevaluation_gate_test.pyinto a local directory and runpython3 -m unittest evaluation_gate_test.py. Verify that all five unit tests pass. - Add a precision-at-recall check: Extend
GatePolicyandEvaluationGateto compute Precision at a fixed recall threshold (for example, Precision when Recall equals 50%) and enforce a minimum floor. - Simulate a slice degradation scenario: Generate a synthetic dataset with two customer regions (
NorthAmericaandEurope). Create predictions where the candidate outperforms the champion globally, but drops performance inEuropeby 10%. Verify that the gate catches the regression. - Implement bootstrap confidence intervals: Write a function that draws five hundred bootstrap resamples of test predictions and outputs the 95% confidence interval for candidate ROC AUC.