A feature distribution can look normal even when every value is old. A schema failure can also remove rows from a distribution calculation, making an alert disappear. One combined healthy flag hides those differences.
When you need this. Use separate data signals when invalid records, old features and a changed feature distribution require different investigations.
Schema checks ask whether an event has the required fields and finite numeric measurements. Availability asks whether it was published by the observation time. Freshness measures age from feature_at, not its recent publication. A distribution signal compares values only after those checks. None of these signals supplies outcome labels or directly measures prediction accuracy.
1. Follow the decision boundary
signals() classifies every event as schema-invalid, unavailable, stale or fresh. Only fresh valid events contribute area values. The authored reference has four values, half at or below 12,500. The signal is the absolute difference between that share and the current eligible share, with minimum sample size four and alert threshold 0.5.
The lab reuses an exact copy of the recorded Rice predictor, bundle and dataset from registry and serving. It performs no training and uses no cloud service. Monitoring fixtures are explicitly authored; the rollout parameter variant is labeled in the result. Keep the complete extracted mlops-rollout-monitoring/ directory together; Python’s standard library is sufficient.
Read the implementation for this activity
"""Compute independent signals and delayed-label coverage from authored fixtures."""
import math
from predictor import CONTRACT,baseline
def integer(v):
if type(v) is not int or v<0:raise ValueError('nonnegative integer clock required')
return v
def unique(rows):
if not isinstance(rows,list) or any(not isinstance(r,dict) or not isinstance(r.get('id'),str) or not 1<=len(r['id'])<=80 for r in rows):
raise ValueError('records need bounded string identities')
ids=[r['id'] for r in rows]
if len(ids)!=len(set(ids)):raise ValueError('duplicate event identity')
def signals(events,reference,now,max_age=15,cutpoint=12500):
integer(now);integer(max_age);unique(events)
if not reference or any(type(v) not in (int,float) or not math.isfinite(v) for v in reference):
raise ValueError('finite nonempty reference required')
counts={'schema_invalid':0,'unavailable':0,'stale':0,'fresh':0};areas=[]
for event in events:
try:
if set(event)!={'id','feature_at','available_at','contract','features'}:raise ValueError('schema')
feature_at=integer(event['feature_at']);available=integer(event['available_at'])
if available<feature_at or event['contract']!=CONTRACT:raise ValueError('schema')
values=event['features']
if not isinstance(values,dict) or set(values)!=set(baseline.FEATURES):raise ValueError('schema')
if any(type(v) not in (int,float) or not math.isfinite(v) for v in values.values()):raise ValueError('schema')
except (ValueError,TypeError,OverflowError):counts['schema_invalid']+=1;continue
if feature_at>now or available>now:counts['unavailable']+=1
elif now-feature_at>max_age:counts['stale']+=1
else:counts['fresh']+=1;areas.append(values['area'])
reference_share=sum(v<=cutpoint for v in reference)/len(reference)
distance=abs(sum(v<=cutpoint for v in areas)/len(areas)-reference_share) if len(areas)>=4 else None
return {'events':len(events),'counts':counts,'distribution':{'feature':'area','cutpoint':cutpoint,
'reference_rows':len(reference),'eligible_rows':len(areas),'minimum_rows':4,
'reference_low_share':reference_share,'absolute_share_difference':distance,
'threshold':0.5,'alert':distance>=0.5 if distance is not None else None}}
def outcomes(predictions,labels,as_of):
integer(as_of);unique(predictions);unique(labels)
expected={p['id']:p for p in predictions};by_id={r['id']:r for r in labels}
if not set(by_id)<=set(expected):raise ValueError('unknown label identity')
for p in predictions:
if set(p)!={'id','predicted_at','model','contract','label'} or not isinstance(p['model'],str) or not p['model']:raise ValueError('prediction schema')
integer(p['predicted_at'])
if p['label'] not in baseline.LABELS or p['contract']!=CONTRACT:raise ValueError('prediction contract')
if len({p['model'] for p in predictions})>1:raise ValueError('select one model cohort before joining outcomes')
for row in labels:
if set(row)!={'id','ready_at','label'}:raise ValueError('label schema')
ready=integer(row['ready_at']);p=expected[row['id']]
if row['label'] not in baseline.LABELS or ready<p['predicted_at']+5:raise ValueError('label not finalized after horizon')
eligible=[p for p in predictions if p['predicted_at']<=as_of]
ready=[p for p in eligible if p['id'] in by_id and by_id[p['id']]['ready_at']<=as_of]
correct=sum(p['label']==by_id[p['id']]['label'] for p in ready)
return {'as_of':as_of,'eligible_predictions':len(eligible),'ready_labels':len(ready),
'pending':sum(p['id'] in by_id and by_id[p['id']]['ready_at']>as_of for p in eligible),
'missing':sum(p['id'] not in by_id for p in eligible),'correct':correct,
'coverage':len(ready)/len(eligible) if eligible else None,
'accuracy_on_ready':correct/len(ready) if ready else None,
'ready_prediction_ids':[p['id'] for p in ready]}
def action(signal,quality,review):
# This explicit teaching policy selects a review action; it never starts training.
if any(signal['counts'][key] for key in ('schema_invalid','stale','unavailable')):return 'investigate_data'
if quality['coverage'] is None or quality['coverage']<0.75:return 'wait_for_labels'
if quality['accuracy_on_ready'] is not None and quality['accuracy_on_ready']<0.75:
if review=={'data_issue_ruled_out':True,'persistent_failure_reviewed':True,'new_labels_reviewed':True}:
return 'prepare_retraining_review'
return 'investigate_quality'
return 'monitor_distribution' if signal['distribution']['alert'] else 'continue_monitoring'Download the complete lab, extract it and open mlops-rollout-monitoring/. The candidate is an intentionally altered parameter fixture used to demonstrate routing. It has not been approved by the earlier quality gate for a real release.
2. Run the focused activity
Run python exercise.py signals. The normal batch has four fresh rows and difference zero. The schema-failure batch retains one invalid count and only three eligible rows, so its distribution result is null. The stale batch records four stale rows and no distribution estimate. The shifted batch has four fresh rows, none below the cutpoint, giving difference 0.5 and an alert.
python exercise.py signals{
"mode": "authored event and finalized-outcome fixtures; not production telemetry",
"batches": {
"normal": {
"events": 4,
"counts": {
"schema_invalid": 0,
"unavailable": 0,
"stale": 0,
"fresh": 4
},
"distribution": {
"feature": "area",
"cutpoint": 12500,
"reference_rows": 4,
"eligible_rows": 4,
"minimum_rows": 4,
"reference_low_share": 0.5,
"absolute_share_difference": 0.0,
"threshold": 0.5,
"alert": false
}
},
"schema_failure": {
"events": 4,
"counts": {
"schema_invalid": 1,
"unavailable": 0,
"stale": 0,
"fresh": 3
},
"distribution": {
"feature": "area",
"cutpoint": 12500,
"reference_rows": 4,
"eligible_rows": 3,
"minimum_rows": 4,
"reference_low_share": 0.5,
"absolute_share_difference": null,
"threshold": 0.5,
"alert": null
}
},
"stale": {
"events": 4,
"counts": {
"schema_invalid": 0,
"unavailable": 0,
"stale": 4,
"fresh": 0
},
"distribution": {
"feature": "area",
"cutpoint": 12500,
"reference_rows": 4,
"eligible_rows": 0,
"minimum_rows": 4,
"reference_low_share": 0.5,
"absolute_share_difference": null,
"threshold": 0.5,
"alert": null
}
},
"distribution_shift": {
"events": 4,
"counts": {
"schema_invalid": 0,
"unavailable": 0,
"stale": 0,
"fresh": 4
},
"distribution": {
"feature": "area",
"cutpoint": 12500,
"reference_rows": 4,
"eligible_rows": 4,
"minimum_rows": 4,
"reference_low_share": 0.5,
"absolute_share_difference": 0.5,
"threshold": 0.5,
"alert": true
}
}
},
"future_availability": {
"events": 4,
"counts": {
"schema_invalid": 0,
"unavailable": 1,
"stale": 0,
"fresh": 3
},
"distribution": {
"feature": "area",
"cutpoint": 12500,
"reference_rows": 4,
"eligible_rows": 3,
"minimum_rows": 4,
"reference_low_share": 0.5,
"absolute_share_difference": null,
"threshold": 0.5,
"alert": null
}
},
"duplicate_refusal": "duplicate event identity",
"distribution_definition": "Absolute difference in area<=12500 shares; authored threshold 0.5 and minimum four fresh valid rows. No significance test."
}The raw result, execution metadata and locally generated actual-output report retain the computed counts and source identities. Opening the report does not repeat execution. --record deliberately replaces that stage’s saved files; ordinary execution leaves them unchanged.

3. Inspect refusals before acting
| Failure condition | Required behavior |
|---|---|
| Duplicate or invalid reference | Refuse duplicate events and nonfinite reference values before counting distributions. |
| Future or stale feature data | A feature published after the cutoff is unavailable; an old feature is stale even when its schema is valid. |
| Insufficient eligible distribution rows | Below the minimum eligible sample count, leave the distribution comparison undefined. |
Duplicate event identities refuse the batch rather than counting the same event twice. A publication after now is unavailable even when its feature timestamp is earlier. test_duplicate_events_and_nonfinite_reference_refused and test_freshness_boundary_and_future_publication check these boundaries. Age 15 is accepted; age 16 is stale.
python -m unittest -v test_continuation.pyPredict the result if you delete one value from the shifted batch. Check the minimum sample rule before interpreting the alert. Explain why falling below the sample minimum must not be reported as a recovered distribution.
How the result should guide the next step
All monitoring events and clocks are explicitly authored in events.json. The computed counts are actual local results, not production telemetry. This one-feature proportion difference is a teaching signal, not a significance test or general drift detector. An insufficient sample is unknown, not a clean bill of health. Earlier schema and freshness counters remain visible.
Check your understanding
Read the explanation
The sample contract is not satisfied. Retaining counts keeps the unknown result distinct from a measured zero difference.
References
Official primary sources checked October 24, 2026. The local records supply this lesson’s numbers and refusals.
- Google ML Crash Course: monitoring: distinguish data checks, distributions and live model quality.
- Python hashlib: deterministic byte digests used by this local implementation.
- Python HTTP server: local server APIs and production limitations.
- Cloud Deploy canary concepts: subset rollout terminology. This lab does not run Cloud Deploy or reproduce its orchestration.