aiml
SearchMy learning
Menu

Lesson 18 of 19Module 6

Distinguish schema, freshness and distribution signals

Validate event fields and feature values before considering freshness. A local executable Rice rollout and monitoring continuation.

Prerequisites

Validate incoming data; check feature freshness and training-serving parity; Python dictionaries and simple proportions.

Preparation and term definitions

By the end, you can

  • Validate event fields and feature values before considering freshness.
  • Separate unavailable, stale and fresh observations using explicit clock boundaries.
  • Compute a declared feature-distribution signal while retaining excluded-row counts.
Runtime
Python 3.12+
Dependencies
Standard library only

Lab code, tests and fixtures (ZIP, 3.1 MB)Quiz check your understanding

Progress is saved in this browser.

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.

Authored eventsSchema + clock checksInvalid / unavailable / staleFresh valid area valuesAuthored reference areasShare difference or unknowncount separatelyfreshminimum fourAuthored eventsSchema + clock checksCount invalid,unavailable and staleFresh valid area valuesAuthored reference areasDifference or unknownexcludedminimum four
Branches follow the implemented local decision. Excluded work and failed checks remain visible rather than being counted as successful output.

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
monitor.pySource · pythonDownload
"""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.

Execute the signals activityTerminal · bash
python exercise.py signals
Actual saved signals resultRecorded output · textDownload
{
  "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.

Actual local signals exercise report, retaining the observed outcomes and failure cases.
Actual local execution. Monitoring computes results from explicitly authored event and label fixtures. Full-resolution capture · Raw result · Capture record.

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.

Check the local failure boundariesTerminal · bash
python -m unittest -v test_continuation.py

Predict 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

Three fresh valid rows remain after schema checks, below the declared minimum of four. How should the distribution result be reported?

Choose one answer, then check your reasoning.

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.

Written by Arjun Prabhulal.

Module 6

Monitor inputs and delayed outcomes