Step 1: Evaluate the candidate
Evaluate the candidate
Cascade Telecom runs a churn model to tell the retention team who to call. The model in production is v1. A team has a candidate, v2, that scores a little better on overall accuracy and wants to ship it. Before anything is published, you evaluate it the way the release will be judged: overall, and per customer segment.
model.py gives you load(path) for a model release, predict(entry, df) for churn probabilities,
load_data() for the held-out set, and the constants SEGMENT ("region"), LABEL ("churn") and
THRESHOLD. The dataset has a region column; rural customers are a small share but they still matter.
Write evaluate(entry, data) returning a report dict: n, accuracy, auc (ROC AUC over the predicted
probabilities), and recall_by_region: for each region, the share of its actual churners the model flags
at the threshold. Run it on both models and watch the rural number.
release.py, the file you edit75 lines
"""Publish the Cascade Telecom churn model. You evaluate the candidate, write its model card from that
evaluation, version it, gate the promotion on per-segment floors, and keep the registry honest so a bad
release can be rolled back."""
import hashlib
import json
from sklearn.metrics import roc_auc_score
import model
# The minimum churn recall we are willing to ship for each region. Rural is small but we will not blind
# ourselves to it.
FLOORS = {"metro": 0.20, "rural": 0.55}
REQUIRED_CARD_SECTIONS = ["intended_use", "training_data", "owner"]
# ---------- Step 1: evaluate the candidate ----------
def evaluate(entry, data):
"""A structured evaluation report for a model on data: overall accuracy and AUC, and the recall of
churners within each region. Returns {"n", "accuracy", "auc", "recall_by_region": {region: recall}}."""
# TODO (Step 1): predict on data, threshold at 0.5; return n, accuracy, auc
# (roc_auc_score on the probabilities) and recall_by_region = churners flagged, per region.
raise NotImplementedError("Step 1: write evaluate()")
# ---------- Step 2: write the model card ----------
def model_card(report, meta, floors=FLOORS):
"""Assemble the model card. The metrics and the list of segments the model underperforms on come from
the evaluation itself, so the card cannot claim more than the eval
shows. meta supplies the human-written sections; raise ValueError if a required one is missing."""
raise NotImplementedError("model_card() arrives in Step 2")
# ---------- Step 3: version the release ----------
def content_hash(entry):
"""A stable sha256 of the model's weights, so an identical model always versions to the same hash and a
changed one does not. Hash only the feature list, scaler stats and coefficients."""
raise NotImplementedError("content_hash() arrives in Step 3")
def next_version(prev, part="minor"):
"""The next semantic version after prev ("major.minor.patch"), bumping the named part and zeroing the
lower ones."""
raise NotImplementedError("next_version() arrives in Step 3")
def release_manifest(entry, card, report):
"""The release record that ties a version to an exact model and its evaluation: name, version, the
model's content hash, the metrics and the full card."""
raise NotImplementedError("release_manifest() arrives in Step 3")
# ---------- Step 4: gate the promotion ----------
def gate(candidate, prod, floors=FLOORS, tol=0.005):
"""Decide whether the candidate report may be promoted over the production report. Block it if overall
accuracy regressed by more than tol, or if any region's recall is below its floor. Returns
{"promote": bool, "reasons": [...]} with a reason for every failure."""
raise NotImplementedError("gate() arrives in Step 4")
# ---------- Step 5: register and roll back ----------
def promote(registry, manifest):
"""Record manifest as a release and make it current, pushing the previous current onto history."""
raise NotImplementedError("promote() arrives in Step 5")
def rollback(registry):
"""Undo the last promotion: make the previous version current again. Raise ValueError if there is no
history to roll back to."""
raise NotImplementedError("rollback() arrives in Step 5")data.csvmodel.pymodel_v1.jsonmodel_v2.jsontry_it.py