Given old and new metric values by slice, return the slices where
a lower-is-better metric regresses more than the allowed amount, and
report slice mismatches or underpowered slices before making a
release decision.
Hidden answer: invariant and Python solution
Invariant: each flagged row has a slice present in both metric
maps, both metric values are finite, the slice has enough examples
for a release gate, new - old is greater than the
non-negative regression budget, and missing, newly added, or
underpowered slices are reported separately instead of being
silently treated as zero.
from math import isfinite
from numbers import Integral, Real
def _finite_number(value, name):
if isinstance(value, bool) or not isinstance(value, Real) or not isfinite(value):
raise ValueError(f"{name} must be a finite number")
return float(value)
def _count(value, name):
if isinstance(value, bool) or not isinstance(value, Integral) or value < 0:
raise ValueError(f"{name} must be a non-negative integer")
return int(value)
def slices_over_budget(old_metrics, new_metrics, budget, slice_counts=None, min_count=1):
budget = _finite_number(budget, "budget")
if budget < 0:
raise ValueError("budget must be non-negative for lower-is-better metrics")
min_count = _count(min_count, "min_count")
if min_count == 0:
raise ValueError("min_count must be positive for a release gate")
missing_in_new = sorted(set(old_metrics) - set(new_metrics))
new_only = sorted(set(new_metrics) - set(old_metrics))
flagged = []
underpowered = []
for name in sorted(set(old_metrics) & set(new_metrics)):
old_value = _finite_number(old_metrics[name], f"{name} old metric")
new_value = _finite_number(new_metrics[name], f"{name} new metric")
if slice_counts is not None:
support = _count(slice_counts.get(name, 0), f"{name} count")
if support < min_count:
underpowered.append({"slice": name, "count": support})
continue
delta = new_value - old_value
if delta > budget:
flagged.append({
"slice": name,
"old": old_value,
"new": new_value,
"delta": delta,
})
return {
"flagged": flagged,
"missing_in_new": missing_in_new,
"new_only": new_only,
"underpowered": underpowered,
}