Model Observability with Assays

How to use Wallaroo Assays to monitor the environment and know when to retrain the model based on changes to data.

This tutorial and the assets can be downloaded as part of the Wallaroo Tutorials repository.

Model Drift Observability with Assays

The Model Insights feature lets you monitor how the environment that your model operates within may be changing in ways that affect it’s predictions so that you can intervene (retrain) in an efficient and timely manner. Changes in the inputs, data drift, can occur due to errors in the data processing pipeline or due to changes in the environment such as user preference or behavior.

This notebook focuses on interactive exploration over historical data. After you are comfortable with how your data has behaved historically, you can schedule this same analysis (called an assay) to automatically run periodically, looking for indications of data drift or concept drift.

In this notebook, we will be running a drift assay on an ONNX model pre-trained to predict house prices.

  • IMPORTANT NOTICE: The following assumes Wallaroo Assays V2 are enabled. This is the default setting for Wallaroo 2024.4 and later. See Set the Assay Version for more details.

Goal

Model insights monitors the output of the spam classifier model over a designated time window and compares it to an expected baseline distribution. We measure the difference between the window distribution and the baseline distribution; large differences indicate that the behavior of the model (or its inputs) has changed from what we expect. This possibly indicates a change that should be accounted for, possibly by retraining the models.

Resources

This tutorial provides the following:

  • Models:
    • models/rf_model.onnx: The champion model that has been used in this environment for some time.
    • Various inputs:
      • smallinputs.df.json: A set of house inputs that tends to generate low house price values.
      • biginputs.df.json: A set of house inputs that tends to generate high house price values.

Prerequisites

  • A deployed Wallaroo instance
  • The following Python libraries installed:
    • wallaroo: The Wallaroo SDK. Included with the Wallaroo JupyterHub service by default.
    • pandas: Pandas, mainly used for Pandas DataFrame

Steps

  • Deploying a sample ML model used to determine house prices based on a set of input parameters.
  • Build an assay baseline from a set of baseline start and end dates, and an assay baseline from a numpy array.
  • Preview the assay and show different assay configurations.
  • Upload the assay.
  • View assay results.
  • Pause and resume the assay.

Import Libraries

The first step will be to import our libraries, and set variables used through this tutorial.

import wallaroo
from wallaroo.object import EntityNotFoundError
from wallaroo.framework import Framework

from IPython.display import display

# used to display DataFrame information without truncating
from IPython.display import display
import pandas as pd
pd.set_option('display.max_colwidth', None)
pd.set_option('display.max_columns', None)

import datetime
import time

workspace_name = f'assay-demonstration-tutorial'
main_pipeline_name = f'assay-demonstration-tutorial'
model_name_control = f'house-price-estimator'
model_file_name_control = './models/rf_model.onnx'

# Set the name of the assay
assay_name=f"house price assay demo"

# ignoring warnings for demonstration
import warnings
warnings.filterwarnings('ignore')

# used to display DataFrame information without truncating
from IPython.display import display
import pandas as pd
pd.set_option('display.max_colwidth', None)
pd.set_option('display.max_columns', None)

Connect to the Wallaroo Instance

The first step is to connect to Wallaroo through the Wallaroo client. The Python library is included in the Wallaroo install and available through the Jupyter Hub interface provided with your Wallaroo environment.

This is accomplished using the wallaroo.Client() command, which provides a URL to grant the SDK permission to your specific Wallaroo environment. When displayed, enter the URL into a browser and confirm permissions. Store the connection into a variable that can be referenced later.

If logging into the Wallaroo instance through the internal JupyterHub service, use wl = wallaroo.Client(). For more information on Wallaroo Client settings, see the Client Connection guide.

# Login through local Wallaroo instance

wl = wallaroo.Client()

Create Workspace

We will create a workspace to manage our pipeline and models. The following variables will set the name of our sample workspace then set it as the current workspace.

Workspace, pipeline, and model names should be unique to each user, so we’ll add in a randomly generated suffix so multiple people can run this tutorial in a Wallaroo instance without effecting each other.

workspace = wl.get_workspace(name=workspace_name, create_if_not_exist=True)

wl.set_current_workspace(workspace)
{'name': 'assay-demonstration-tutorial', 'id': 7, 'archived': False, 'created_by': 'john.hansarick@wallaroo.ai', 'created_at': '2025-04-09T16:05:38.29344+00:00', 'models': [{'name': 'house-price-estimator', 'versions': 5, 'owner_id': '""', 'last_update_time': datetime.datetime(2025, 4, 30, 21, 47, 56, 93452, tzinfo=tzutc()), 'created_at': datetime.datetime(2025, 4, 9, 16, 5, 39, 718384, tzinfo=tzutc())}], 'pipelines': [{'name': 'assay-demonstration-tutorial', 'create_time': datetime.datetime(2025, 4, 9, 16, 6, 7, 269533, tzinfo=tzutc()), 'definition': '[]'}]}

Upload Model

For our example, we will upload the model that has been trained to derive house prices from a variety of inputs. The model file is rf_model.onnx, and is uploaded with the name house-price-estimator.

housing_model_control = (wl.upload_model(model_name_control, 
                                        model_file_name_control, 
                                        framework=Framework.ONNX)
                                        .configure(tensor_fields=["tensor"])
                        )

Build the Pipeline

This pipeline is made to be an example of an existing situation where a model is deployed and being used for inferences in a production environment. We’ll call it assay-demonstration-tutorial, set housing_model_control as a pipeline step, then run a few sample inferences.

This pipeline will be a simple one - just a single pipeline step.

mainpipeline = wl.build_pipeline(main_pipeline_name)

# # clear the steps if used before
mainpipeline.clear()

mainpipeline.add_model_step(housing_model_control)

#minimum deployment config
deploy_config = wallaroo.DeploymentConfigBuilder().replica_count(1).cpus(0.5).memory("1Gi").build()

mainpipeline.deploy(deployment_config = deploy_config, wait_for_status=False)
Deployment initiated for assay-demonstration-tutorial. Please check pipeline status.
nameassay-demonstration-tutorial
created2025-04-09 16:06:07.269533+00:00
last_updated2025-05-07 16:45:43.144479+00:00
deployedTrue
workspace_id7
workspace_nameassay-demonstration-tutorial
archx86
accelnone
tags
versions98812bd0-615c-4acf-8358-93265253450a, f6f61d02-c0dd-4f06-8765-d4d1a04301fb, 53bd5246-cce7-492e-add4-337e639c9176, d356bf98-3d55-4b96-b1fe-da2044ce4dd8, 2b0eb1a9-33b6-4403-aa68-f34ba072163d, ddae3a00-13c8-469b-bda7-6a25ae089166, 64fe4837-7311-4651-b8f9-67cbfdc8a70f, 9ffe4dc5-4078-4864-b6f1-6dd3fe4f1429
stepshouse-price-estimator
publishedFalse
# check the pipeline status before performing an inference

import time
time.sleep(15)

while mainpipeline.status()['status'] != 'Running':
    time.sleep(15)
    display(mainpipeline.status()['status'])

mainpipeline.status()
'Error'
'Starting'
'Starting'
'Running'
{'status': 'Running',
 'details': [],
 'engines': [{'ip': '10.28.0.2',
   'name': 'engine-5577b69f89-vzq2k',
   'status': 'Running',
   'reason': None,
   'details': [],
   'pipeline_statuses': {'pipelines': [{'id': 'assay-demonstration-tutorial',
      'status': 'Running',
      'version': '98812bd0-615c-4acf-8358-93265253450a'}]},
   'model_statuses': {'models': [{'model_version_id': 19,
      'name': 'house-price-estimator',
      'sha': 'e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6',
      'status': 'Running',
      'version': '5a617aa6-77c7-4e69-a631-8114b8e749d3'}]}}],
 'engine_lbs': [{'ip': '10.28.0.3',
   'name': 'engine-lb-5c56f9b58c-lm8lz',
   'status': 'Running',
   'reason': None,
   'details': []}],
 'sidekicks': []}

Testing

We’ll use two inferences as a quick sample inference - one that has a house that should be determined around $700k, the other with a house determined to be around $1.5 million.

normal_input = pd.DataFrame.from_records({"tensor": [[4.0, 2.5, 2900.0, 5505.0, 2.0, 0.0, 0.0, 3.0, 8.0, 2900.0, 0.0, 47.6063, -122.02, 2970.0, 5251.0, 12.0, 0.0, 0.0]]})
result = mainpipeline.infer(normal_input)
display(result)
timein.tensorout.variableanomaly.count
02025-05-07 16:47:09.099[4.0, 2.5, 2900.0, 5505.0, 2.0, 0.0, 0.0, 3.0, 8.0, 2900.0, 0.0, 47.6063, -122.02, 2970.0, 5251.0, 12.0, 0.0, 0.0][718013.7]0
large_house_input = pd.DataFrame.from_records({'tensor': [[4.0, 3.0, 3710.0, 20000.0, 2.0, 0.0, 2.0, 5.0, 10.0, 2760.0, 950.0, 47.6696, -122.261, 3970.0, 20000.0, 79.0, 0.0, 0.0]]})
large_house_result = mainpipeline.infer(large_house_input)
display(large_house_result)
timein.tensorout.variableanomaly.count
02025-05-07 16:47:09.342[4.0, 3.0, 3710.0, 20000.0, 2.0, 0.0, 2.0, 5.0, 10.0, 2760.0, 950.0, 47.6696, -122.261, 3970.0, 20000.0, 79.0, 0.0, 0.0][1514079.4]0

Generate Sample Data

Before creating the assays, we must generate data for the assays to build from.

For this example, we will:

  • Perform sample inferences based on lower priced houses and use that as our baseline.
  • Generate inferences from specific set of high priced houses create inference outputs that will be outside the baseline. This is used in later steps to demonstrate baseline comparison against assay analyses.

Inference Results History Generation

To start the demonstration, we’ll create a set of inferences based on houses with small estimated prices.

We will save the beginning and end periods of inferences to the variables assay_baseline_start and assay_baseline_end. These will be used later to create assay baselines from a range of dates.

small_houses_inputs = pd.read_json('./data/xtest-1k.df.json')
baseline_size = 500

# Where the baseline data will start
assay_baseline_start = datetime.datetime.now(datetime.timezone.utc)

# These inputs will be random samples of small priced houses.
baseline_sample = small_houses_inputs.sample(baseline_size, replace=True).reset_index(drop=True)

# Wait 60 seconds to set this data apart from the rest
# time.sleep(60)
baseline_inference_results = mainpipeline.infer(baseline_sample)

# Set the baseline end

assay_baseline_end = datetime.datetime.now(datetime.timezone.utc)

Generate Numpy Baseline Values

From our inference outputs, we will create an array of numpy values. These are used for assays baselines created from numpy values.

# get the numpy values

# set the results to a non-array value
baseline_results_baseline_df = baseline_inference_results.copy()
baseline_results_baseline_df['variable']=baseline_inference_results['out.variable'].map(lambda x: x[0])
display(baseline_results_baseline_df['variable'])

# set the numpy array
baseline_results = baseline_results_baseline_df['variable'].to_numpy()
0      385982.06
1      448627.80
2      811666.70
3      340764.53
4      437177.97
         ...    
495    437177.97
496    518869.03
497    630865.50
498    317551.28
499    964052.60
Name: variable, Length: 500, dtype: float64

Model Insights via the Wallaroo SDK

Assays generated through the Wallaroo SDK can be previewed, configured, and uploaded to the Wallaroo Ops instance. The following is a condensed version of this process. For full details see the Wallaroo SDK Essentials Guide: Assays Management guide.

Model drift detection with assays using the Wallaroo SDK follows this general process.

  • Define the Baseline: From either historical inference data for a specific model in a pipeline, or from a pre-determine array of data, a baseline is formed.
  • Assay Preview: Once the baseline is formed, we preview the assay and configure the different options until we have the the best method of detecting environment or model drift.
  • Create Assay: With the previews and configuration complete, we upload the assay. The assay will perform an analysis on a regular scheduled based on the configuration.
  • Get Assay Results: Retrieve the analyses and use them to detect model drift and possible sources.
  • Pause/Resume Assay: Pause or restart an assay as needed.

Set the Assay Version

The assay version is set through platform operations, either during installation or via update. Wallaroo platform engineers should inform users which assay version is being used. For Wallaroo 2024.4, Assays V2 is the default.

The Wallaroo SDK is configured to use Assays V2 by setting the following environmental variable:

ASSAYS_V2_ENABLED=True|False

If True (Default), the Wallaroo SDK uses Wallaroo Assay V2. If False, the Wallaroo SDK uses Wallaroo Assays V1. This must match the same Assay setting set for through platform operations. For the Wallaroo 2024.4 SDK, the SDK defaults to Assays V2; no environmental variable is required if using Assays V2.

Define the Baseline

Assay baselines are defined with the wallaroo.client.build_assay method. Through this process we define the baseline from either a range of dates or pre-generated values.

wallaroo.client.build_assay take the following parameters:

ParameterTypeDescription
assay_nameString (Required) - requiredThe name of the assay. Assay names must be unique across the Wallaroo instance.
pipelinewallaroo.pipeline.Pipeline (Required)The pipeline the assay is monitoring.
model_nameString (Optional) / NoneThe name of the model to monitor. This field should only be used to track the inputs/outputs for a specific model step in a pipeline. If no model_name is to be included, then the parameters must be passed a named parameters not positional ones.
iopathString (Required)The iopath of the assay in the format "input/output {field_name} {field_index}", where field_index is required for list values and omitted for scalar values. For example, to monitor the output list field house_price at index 0, the iopath is output house_price 0. For scalar and numpy nan support, these data types are only supported in Wallaroo Assay Version V2.
For data that are scalar values, no index is passed. For example, the iopath for the input scalar field number_of_houses as an input is 'input number_of_houses'. For more details of scalar and nan support, see Iopath Format and NaN Support.
baseline_startdatetime.datetime (Optional)The start time for the inferences to use as the baseline. Must be included with baseline_end. Cannot be included with baseline_data.
baseline_enddatetime.datetime (Optional)The end time of the baseline window. the baseline. Windows start immediately after the baseline window and are run at regular intervals continuously until the assay is deactivated or deleted. Must be included with baseline_start. Cannot be included with baseline_data..
baseline_dataList(numpy.ndarray) (Optional)The baseline data in numpy array format. Cannot be included with either baseline_start or baseline_data.

Note that model_name is an optional parameters when parameters are named. For example:

assay_builder_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

or:

assay_builder_from_dates = wl.build_assay("assays from date baseline", 
                                          mainpipeline, 
                                          None, ## since we are using positional parameters, `None` must be included for the model parameter
                                          "output variable 0",
                                          assay_baseline_start, 
                                          assay_baseline_end)

Baselines are created in one of two mutually exclusive methods:

  • Date Range: The baseline_start and baseline_end retrieves the inference requests and results for the pipeline from the start and end period. This data is summarized and used to create the baseline. For our examples, we’re using the variables assay_baseline_start and assay_baseline_end to represent a range of dates, with assay_baseline_start being set before assay_baseline_end.
  • Numpy Values: The baseline_data sets the baseline from a provided List(numpy.ndarray). This allows assay baselines to be created without first performing inferences in Wallaroo.

Iopath Format

The Input/Output Path (iopath) uses the following format.

  • Input/Output: Whether the data is input for model, or is model’s output.
  • Field: The specific field to monitor.
  • Index: The field index to monitor.
    • For Assays V2 only, scalar values are represented by passing no index.

The following shows setting the iopath via the Wallaroo SDK for List values and scalars.

ListScalar (Assays V2 only)

assay_builder_for_list = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

assay_builder_for_scalar = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

NaN Support

For List(numpy.ndarray) data that includes nan aka “Not a Number” (NaN), nan values are treated in Wallaroo assays as follows:

  • Visualizations: nan values are not discretely represented in visualizations. These include methods including Analysis Chart.
  • SDK Calls: nan values in requests and responses are handled as null values. These include the following scenarios:
    • Baselines imports.
    • Querying baseline data.
    • Querying assays results in preview mode.
    • Querying assay results after assay upload and analysis.
  • Baseline metrics: Baseline metrics created from imported values (aka numpy) and date based baselines from the Wallaroo Dashboard and the Wallaroo SDK are treated as follows:
    • Baseline stats ignore nan when calculating: mean, median, std dev, min, max.
    • Baseline stats include nan when calculating count.
  • Baseline binning includes nan for:
    • Score Metrics of PSI, SUMDIFF and MAXDIFF for both preview aka “interactive run” and post assay upload analysis factor nan values into the window score calculation and the associated binning scheme.
    • NaN’s are treated as negative infinity but included in the left most bin.
Baseline Upload with NaN

When uploading baseline data via the Wallaroo Dashboard, verify that the scalar field is set to None and the imported baseline csv nan values must be one of the following:

  • empty double quotes “”
  • empty single quotes ‘’
  • empty values

The following are examples of NaN within baseline imported data.

nan values as empty values.nan represented as double quotes:nan represented as single quotes:
0.70193326,
0.47008988,
0.7616768,
0.9431376,
0.50103927,
,
0.89329976,
,
,
,
0.58292973,
0.48995697,
,
0.9030761,
0.7702084,
0.9390605,
0.8298963,
0.67003745,
,
0.70193326,
0.47008988,
0.7616768,
0.9431376,
0.50103927,
"",
0.89329976,
"",
"",
"",
0.58292973,
0.48995697,
"",
0.9030761,
0.7702084,
0.9390605,
0.8298963,
0.67003745,
"",
""
0.70193326,
0.47008988,
0.7616768,
0.9431376,
0.50103927,
'',
0.89329976,
'',
'',
'',
0.58292973,
0.48995697,
'',
0.9030761,
0.7702084,
0.9390605,
0.8298963,
0.67003745,
'',
''

Define the Baseline Example

This example shows two methods of defining the baseline for an assay:

  • "assays from date baseline": This assay uses historical inference requests to define the baseline. This assay is saved to the variable assay_builder_from_dates.
  • "assays from numpy": This assay uses a pre-generated numpy array to define the baseline. This assay is saved to the variable assay_builder_from_numpy.

In both cases, the following parameters are used:

ParameterValue
assay_name"assays from date baseline" and "assays from numpy"
pipelinemainpipeline: A pipeline with a ML model that predicts house prices. The output field for this model is variable.
iopathThese assays monitor the model’s output field variable at index 0 for the pipeline. From this, the iopath setting is "output variable 0".

The difference between the two assays’ parameters determines how the baseline is generated.

  • "assays from date baseline": Uses the baseline_start and baseline_end to set the time period of inference requests and results to gather data from.
  • "assays from numpy": Uses a pre-generated numpy array as for the baseline data.

First we generate an assay baseline from a range of historical inferences performed through the specified pipeline deployment.

# Build the assay, based on the start and end of our baseline time, 
# and tracking the output variable index 0

# display(assay_baseline_start)
# display(assay_baseline_end)

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# create the baseline from the dates
assay_baseline_run_from_dates = assay_baseline_from_dates.build().interactive_baseline_run()
# Build the assay based on the numpy array
# and tracking the output variable index 0

# display(assay_baseline_start)
# display(assay_baseline_end)

assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# # create the baseline from the dates
assay_baseline_run_from_numpy = assay_baseline_from_numpy.build().interactive_baseline_run()

Baseline Chart

The baseline chart is displayed with wallaroo.assay.AssayAnalysis.chart(), which returns a chart with:

  • baseline mean: The mean value of the baseline values.
  • baseline median: The median value of the baseline values.
  • bin_mode: The binning mode. See Binning Mode
  • aggregation: The aggregation type. See Aggregation Options
  • metric: The assay’s metric type. See Score Metric
  • weighted: Whether the binning mode is weighted. See Binning Mode

The first chart is from an assay baseline generated from a set of inferences across a range of dates.

assay_baseline_run_from_dates.chart()
baseline mean = 553768.87409375
baseline median = 452115.8125
bin_mode = Quantile
aggregation = Density
metric = PSI
weighted = False
assay_baseline_run_from_numpy.chart()
baseline mean = 553768.87562
baseline median = 452115.79000000004
bin_mode = Quantile
aggregation = Density
metric = PSI
weighted = False

Baseline DataFrame

The method wallaroo.assay_config.AssayBuilder.baseline_dataframe returns a DataFrame of the assay baseline generated from the provided parameters. This includes:

  • metadata: The inference metadata with the model information, inference time, and other related factors.
  • in data: Each input field assigned with the label in.{input field name}.
  • out data: Each output field assigned with the label out.{output field name}

Note that for assays generated from numpy values, there is only the out data based on the supplied baseline data.

In the following example, the baseline DataFrame is retrieved.

This baseline DataFrame is from an assay baseline generated from a set of inferences across a range of dates.

display(assay_baseline_from_dates.baseline_dataframe())
timemetadatainput_tensor_0input_tensor_1input_tensor_2input_tensor_3input_tensor_4input_tensor_5input_tensor_6input_tensor_7input_tensor_8input_tensor_9input_tensor_10input_tensor_11input_tensor_12input_tensor_13input_tensor_14input_tensor_15input_tensor_16input_tensor_17output_variable_0
01746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}5.01.752340.09148.02.00.00.03.07.02340.00.047.4232-122.3241390.010019.057.00.00.0385982.06250
11746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}3.01.001220.03000.01.50.00.03.06.01220.00.047.6506-122.3461350.03000.0114.00.00.0448627.81250
21746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}5.03.253030.020446.02.00.02.03.09.02130.0900.047.6133-122.1062890.020908.038.00.00.0811666.68750
31746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}3.02.251180.014258.02.00.00.03.07.01180.00.047.7112-122.2381860.010390.027.00.00.0340764.53125
41746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}2.01.751670.06460.01.00.00.03.08.01670.00.047.7123-122.0272170.06254.010.00.00.0437177.96875
..................................................................
4951746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}3.02.501830.08133.01.00.00.03.08.01390.0440.047.7478-122.2472310.011522.019.00.00.0437177.96875
4961746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}4.02.752930.022000.01.00.03.04.09.01580.01350.047.3227-122.3842930.09758.036.00.00.0518869.03125
4971746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}4.03.252050.05000.02.00.00.04.08.01370.0680.047.6235-122.2981720.05000.027.00.00.0630865.50000
4981746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}4.01.752000.08700.01.00.00.05.07.01010.0990.047.3740-122.1411490.07350.039.00.00.0317551.28125
4991746636429634{'last_model': '{"model_name":"house-price-estimator","model_sha":"e22a0831aafd9917f3cc87a15ed267797f80e2afa12ad7d8810ca58f173b8cc6"}', 'pipeline_version': '98812bd0-615c-4acf-8358-93265253450a', 'elapsed': [2139061, 3978743], 'dropped': [], 'partition': 'engine-5577b69f89-vzq2k'}4.02.002580.04500.02.00.00.04.09.01850.0730.047.6245-122.3162590.04100.0110.00.00.0964052.62500

500 rows × 21 columns

display(assay_baseline_from_numpy.baseline_dataframe())
output_variable_0
0385982.06
1448627.80
2811666.70
3340764.53
4437177.97
......
495437177.97
496518869.03
497630865.50
498317551.28
499964052.60

500 rows × 1 columns

Baseline Stats

The method wallaroo.assay.AssayAnalysis.baseline_stats() returns a pandas.core.frame.DataFrame of the baseline stats.

The baseline stats for each assay are displayed in the examples below.

This baseline states DataFrame is from an assay baseline generated from a set of inferences across a range of dates.

assay_baseline_run_from_dates.baseline_stats()
Baseline
count500
min236238.671875
max2016006.125
mean553768.874094
median452115.8125
std295421.65625
startNone
endNone
assay_baseline_from_numpy.baseline_dataframe()
output_variable_0
0385982.06
1448627.80
2811666.70
3340764.53
4437177.97
......
495437177.97
496518869.03
497630865.50
498317551.28
499964052.60

500 rows × 1 columns

Baseline Bins

The method wallaroo.assay.AssayAnalysis.baseline_bins a simple dataframe to with the edge/bin data for a baseline.

These baseline bins DataFrame is from an assay baseline generated from a set of inferences across a range of dates.

assay_baseline_run_from_dates.baseline_bins()
b_edgesb_edge_namesb_aggregated_valuesb_aggregation
0338418.84375< 20%0.202Aggregation.DENSITY
1443352.37520% - 40%0.198Aggregation.DENSITY
2546631.937540% - 60%0.206Aggregation.DENSITY
3714289.187560% - 80%0.194Aggregation.DENSITY
4INFINITY> 80%0.200Aggregation.DENSITY
assay_baseline_run_from_numpy.baseline_bins()
b_edgesb_edge_namesb_aggregated_valuesb_aggregation
0338418.84< 20%0.202Aggregation.DENSITY
1443352.3820% - 40%0.198Aggregation.DENSITY
2546631.9440% - 60%0.206Aggregation.DENSITY
3714289.21260% - 80%0.194Aggregation.DENSITY
4INFINITY> 80%0.200Aggregation.DENSITY

Baseline Histogram Chart

The method wallaroo.assay_config.AssayBuilder.baseline_histogram returns a histogram chart of the assay baseline generated from the provided parameters.

These chart is from an assay baseline generated from a set of inferences across a range of dates.

assay_baseline_from_dates.baseline_histogram()
assay_baseline_from_numpy.baseline_histogram()

Baseline KDE Chart

The method wallaroo.assay_config.AssayBuilder.baseline_kde returns a Kernel Density Estimation (KDE) chart of the assay baseline generated from the provided parameters.

These chart is from an assay baseline generated from a set of inferences across a range of dates.

assay_baseline_from_dates.baseline_kde()
assay_baseline_from_numpy.baseline_kde()

Baseline ECDF Chart

The method wallaroo.assay_config.AssayBuilder.baseline_ecdf returns a Empirical Cumulative Distribution Function (CDF) chart of the assay baseline generated from the provided parameters.

These chart is from an assay baseline generated from a set of inferences across a range of dates.

assay_baseline_from_dates.baseline_ecdf()
assay_baseline_from_numpy.baseline_ecdf()

Assay Preview

Now that the baseline is defined, we look at different configuration options and view how the assay baseline and results changes. Once we determine what gives us the best method of determining model drift, we can create the assay.

Analysis List Chart Scores

Analysis List scores show the assay scores for each assay result interval in one chart. Values that are outside of the alert threshold are colored red, while scores within the alert threshold are green.

Assay chart scores are displayed with the method wallaroo.assay.AssayAnalysisList.chart_scores, with ability to display an optional title with the chart. This takes the following parameters:

ParameterTypeDescription
titleString (Optional)The title to assign the chart.
nth_x_tickInteger (Optional) (Default: 4)The density of the x ticks.
startDateTime (Optional)The start time of the chart. If start is set, the parameter end must be set.
endDateTime (Optional)The end time of the chart. If end is set, the parameter start must be set.

The following example shows retrieving the assay results and displaying the chart scores. From our example, we have two windows - the first should be green, and the second is red showing that values were outside the alert threshold.

The following images show how the assay charts change when the nth_x_tick changes from the default to 10. Note that the tutorial code will only generate one data point - these images are for demonstration purposes.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show the analyses chart
assay_results_from_dates.chart_scores()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show the analysis chart from the numpy baseline based assay
assay_results_from_numpy.chart_scores()

The following chart uses the nth_x_tick setting to space out the x-axis labels further.

# set different nth_x_tick
assay_results_from_dates.chart_scores(nth_x_tick=10)
assay_results_from_numpy.chart_scores(nth_x_tick=10)

Analysis Chart

The method wallaroo.assay.AssayAnalysis.chart() displays a comparison between the baseline and an interval of inference data.

This is compared to the Chart Scores, which is a list of all of the inference data split into intervals, while the Analysis Chart shows the breakdown of one set of inference data against the baseline.

Score from the Analysis List Chart Scores and each element from the Analysis List DataFrame generates

The following fields are included.

FieldTypeDescription
baseline meanFloatThe mean of the baseline values.
window meanFloatThe mean of the window values.
baseline medianFloatThe median of the baseline values.
window medianFloatThe median of the window values.
bin_modeStringThe binning mode used for the assay.
aggregationStringThe aggregation mode used for the assay.
metricStringThe metric mode used for the assay.
weightedBoolWhether the bins were manually weighted.
scoreFloatThe score from the assay window.
scoresList(Float)The score from each assay window bin.
indexInteger/NoneThe window index. Interactive assay runs are None.
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display one of the analysis from the total results
assay_results_from_dates[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show the analysis chart from the numpy baseline based assay
assay_results_from_numpy[0].chart()

Analysis List DataFrame

wallaroo.assay.AssayAnalysisList.to_dataframe() returns a DataFrame showing the assay results for each window aka individual analysis. This DataFrame contains the following fields:

FieldTypeDescription
assay_idString/NoneThe assay id in UUID format. Only provided from uploaded and executed assays.
nameString/NoneThe name of the assay. Only provided from uploaded and executed assays.
iopathString/NoneThe iopath of the assay. Only provided from uploaded and executed assays.
scoreFloatThe assay score.
startDateTimeThe DateTime start of the assay window.
minFloatThe minimum value in the assay window.
maxFloatThe maximum value in the assay window.
meanFloatThe mean value in the assay window.
medianFloatThe median value in the assay window.
stdFloatThe standard deviation value in the assay window.
warning_thresholdFloat/NoneThe warning threshold of the assay window.
alert_thresholdFloat/NoneThe alert threshold of the assay window.
statusStringThe assay window status. Values are:
  • OK: The score is within accepted thresholds.
  • Warning: The score has triggered the warning_threshold if exists, but not the alert_threshold.
  • Alert: The score has triggered the the alert_threshold.

For this example, the assay analysis list DataFrame is listed.

From this tutorial, we should have 2 windows of dta to look at, each one minute apart. The first window should show status: OK, with the second window with the very large house prices will show status: alert

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display the dataframe from the analyses
assay_results_from_dates.to_dataframe()
idassay_idassay_nameiopathpipeline_idpipeline_nameworkspace_idworkspace_namescorestartminmaxmeanmedianstdwarning_thresholdalert_thresholdstatus
00d5aa4404-0509-4e40-ba6c-6b0a96cb508cassay-demonstration-tutorial assayout.variable.01assay-demonstration-tutorial7assay-demonstration-tutorial0.0127162025-04-29 22:20:27.526833+00:00236238.6718752005883.125538249.025087453247.0247711.921875None0.25Ok
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

assay_results_from_numpy.to_dataframe()
idassay_idassay_nameiopathpipeline_idpipeline_nameworkspace_idworkspace_namescorestartminmaxmeanmedianstdwarning_thresholdalert_thresholdstatus
00d529952d-f817-4e58-a2df-b222cc445869assay-demonstration-tutorial assayout.variable.01assay-demonstration-tutorial7assay-demonstration-tutorial0.0102072025-04-29 22:20:27.526833+00:00236238.6718752005883.125538249.025087453247.0247711.921875None0.25Ok

Analysis List Full DataFrame

wallaroo.assay.AssayAnalysisList.to_full_dataframe() returns a DataFrame showing all values, including the inputs and outputs from the assay results for each window aka individual analysis. This DataFrame contains the following fields:

pipeline_id	warning_threshold	bin_index	created_at
FieldTypeDescription
window_startDateTimeThe date and time when the window period began.
analyzed_atDateTimeThe date and time when the assay analysis was performed.
elapsed_millisIntegerHow long the analysis took to perform in milliseconds.
baseline_summary_countIntegerThe number of data elements from the baseline.
baseline_summary_minFloatThe minimum value from the baseline summary.
baseline_summary_maxFloatThe maximum value from the baseline summary.
baseline_summary_meanFloatThe mean value of the baseline summary.
baseline_summary_medianFloatThe median value of the baseline summary.
baseline_summary_stdFloatThe standard deviation value of the baseline summary.
baseline_summary_edges_{0…n}FloatThe baseline summary edges for each baseline edge from 0 to number of edges.
summarizer_typeStringThe type of summarizer used for the baseline. See wallaroo.assay_config for other summarizer types.
summarizer_bin_weightsList / NoneIf baseline bin weights were provided, the list of those weights. Otherwise, None.
summarizer_provided_edgesList / NoneIf baseline bin edges were provided, the list of those edges. Otherwise, None.
statusStringThe assay window status. Values are:
  • OK: The score is within accepted thresholds.
  • Warning: The score has triggered the warning_threshold if exists, but not the alert_threshold.
  • Alert: The score has triggered the the alert_threshold.
idInteger/NoneThe id for the window aka analysis. Only provided from uploaded and executed assays.
assay_idString/NoneThe assay id in UUID format. Only provided from uploaded and executed assays.
pipeline_idInteger/NoneThe pipeline id. Only provided from uploaded and executed assays.
warning_thresholdFloatThe warning threshold set for the assay.
warning_thresholdFloatThe warning threshold set for the assay.
bin_indexInteger/NoneThe bin index for the window aka analysis.
created_atDatetime/NoneThe date and time the window aka analysis was generated. Only provided from uploaded and executed assays.

For this example, full DataFrame from an assay preview is generated.

From this tutorial, we should have 2 windows of dta to look at, each one minute apart. The first window should show status: OK, with the second window with the very large house prices will show status: alert

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display the full dataframe from the analyses
assay_results_from_dates.to_full_dataframe()
idassay_idwindow_startanalyzed_atelapsed_millispipeline_idworkspace_idworkspace_namebaseline_summary_aggregated_values_0baseline_summary_aggregated_values_1baseline_summary_aggregated_values_2baseline_summary_aggregated_values_3baseline_summary_aggregated_values_4baseline_summary_aggregationbaseline_summary_bins_edges_0baseline_summary_bins_edges_1baseline_summary_bins_edges_2baseline_summary_bins_edges_3baseline_summary_bins_edges_4baseline_summary_bins_labels_0baseline_summary_bins_labels_1baseline_summary_bins_labels_2baseline_summary_bins_labels_3baseline_summary_bins_labels_4baseline_summary_bins_mode_Quantilebaseline_summary_namebaseline_summary_statistics_countbaseline_summary_statistics_maxbaseline_summary_statistics_meanbaseline_summary_statistics_medianbaseline_summary_statistics_minbaseline_summary_statistics_stdbaseline_summary_endbaseline_summary_startwindow_summary_aggregated_values_0window_summary_aggregated_values_1window_summary_aggregated_values_2window_summary_aggregated_values_3window_summary_aggregated_values_4window_summary_bins_edges_0window_summary_bins_edges_1window_summary_bins_edges_2window_summary_bins_edges_3window_summary_bins_edges_4window_summary_bins_labels_0window_summary_bins_labels_1window_summary_bins_labels_2window_summary_bins_labels_3window_summary_bins_labels_4window_summary_bins_mode_Quantilewindow_summary_statistics_countwindow_summary_statistics_maxwindow_summary_statistics_meanwindow_summary_statistics_medianwindow_summary_statistics_minwindow_summary_statistics_stdwindow_summary_endwindow_summary_startwarning_thresholdalert_thresholdbin_indexsummarizer_UnivariateContinuous_aggregationsummarizer_UnivariateContinuous_bin_mode_Quantilesummarizer_UnivariateContinuous_metricsummarizer_UnivariateContinuous_bin_weightsstatuscreated_atscorescores_0scores_1scores_2scores_3scores_4
008038ec07-40f0-4c7b-84af-00c6faa721542025-04-29T22:20:27.526833+00:002025-04-30 22:28:54.033236+00:007717assay-demonstration-tutorial0.20.210.190.20.2Density319918.59375435628.71875535686.0696474.6875INFINITY< 20%20% - 40%40% - 60%60% - 80%> 80%5out.variable.05002005883.125530712.689594448627.8125236238.671875273499.25NoneNone0.1713150.1992030.1912350.1972110.241036319918.59375435628.71875535686.0696474.6875INFINITY< 20%20% - 40%40% - 60%60% - 80%> 80%55022005883.125538249.025087453247.0236238.671875247711.9218752025-04-30T22:20:27.526833+00:002025-04-29T22:20:27.526833+00:00None0.25NoneDensity5PSINoneOkNone0.0127160.0044410.000570.0000080.0000390.007658
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

assay_results_from_numpy.to_full_dataframe()
idassay_idwindow_startanalyzed_atelapsed_millispipeline_idworkspace_idworkspace_namebaseline_summary_aggregated_values_0baseline_summary_aggregated_values_1baseline_summary_aggregated_values_2baseline_summary_aggregated_values_3baseline_summary_aggregated_values_4baseline_summary_aggregationbaseline_summary_bins_edges_0baseline_summary_bins_edges_1baseline_summary_bins_edges_2baseline_summary_bins_edges_3baseline_summary_bins_edges_4baseline_summary_bins_labels_0baseline_summary_bins_labels_1baseline_summary_bins_labels_2baseline_summary_bins_labels_3baseline_summary_bins_labels_4baseline_summary_bins_mode_Quantilebaseline_summary_namebaseline_summary_statistics_countbaseline_summary_statistics_maxbaseline_summary_statistics_meanbaseline_summary_statistics_medianbaseline_summary_statistics_minbaseline_summary_statistics_stdbaseline_summary_endbaseline_summary_startwindow_summary_aggregated_values_0window_summary_aggregated_values_1window_summary_aggregated_values_2window_summary_aggregated_values_3window_summary_aggregated_values_4window_summary_bins_edges_0window_summary_bins_edges_1window_summary_bins_edges_2window_summary_bins_edges_3window_summary_bins_edges_4window_summary_bins_labels_0window_summary_bins_labels_1window_summary_bins_labels_2window_summary_bins_labels_3window_summary_bins_labels_4window_summary_bins_mode_Quantilewindow_summary_statistics_countwindow_summary_statistics_maxwindow_summary_statistics_meanwindow_summary_statistics_medianwindow_summary_statistics_minwindow_summary_statistics_stdwindow_summary_endwindow_summary_startwarning_thresholdalert_thresholdbin_indexsummarizer_UnivariateContinuous_aggregationsummarizer_UnivariateContinuous_bin_mode_Quantilesummarizer_UnivariateContinuous_metricsummarizer_UnivariateContinuous_bin_weightsstatuscreated_atscorescores_0scores_1scores_2scores_3scores_4
007e95f8a4-fd02-48f7-b67e-b41cce8295122025-04-29T22:20:27.526833+00:002025-04-30 22:29:13.220637+00:009217assay-demonstration-tutorial0.2220.1820.2020.1940.2Density340764.53444933.25557391.25712519.68INFINITY< 20%20% - 40%40% - 60%60% - 80%> 80%5out.variable.05002005883.1535937.83588451058.3236238.67244197.003014NoneNone0.1892430.2131470.2011950.1932270.203187340764.53444933.25557391.25712519.68INFINITY< 20%20% - 40%40% - 60%60% - 80%> 80%55022005883.125538249.025087453247.0236238.671875247711.9218752025-04-30T22:20:27.526833+00:002025-04-29T22:20:27.526833+00:00None0.25NoneDensity5PSINoneOkNone0.0102070.0052290.0049210.0000030.0000030.00005

Analysis Compare Basic Stats

The method wallaroo.assay.AssayAnalysis.compare_basic_stats returns a DataFrame comparing one set of inference data against the baseline.

This is compared to the Analysis List DataFrame, which is a list of all of the inference data split into intervals, while the Analysis Compare Basic Stats shows the breakdown of one set of inference data against the baseline.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display one analysis against the baseline
assay_results_from_dates[0].compare_basic_stats()
BaselineWindowdiffpct_diff
count500.0502.02.00.4
max2005883.1252005883.1250.00.0
mean530712.689594538249.0250877536.3354931.420041
median448627.8125453247.04619.18751.029626
min236238.671875236238.6718750.00.0
std273499.25247711.921875-25787.328125-9.428665
startNone2025-04-29T22:20:27.526833+00:00NaNNaN
endNone2025-04-30T22:20:27.526833+00:00NaNNaN
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# assay_results_from_numpy.to_full_dataframe()

assay_results_from_numpy[0].compare_basic_stats()
BaselineWindowdiffpct_diff
count500.0502.02.00.4
max2005883.12005883.1250.0250.000001
mean535937.83588538249.0250872311.1892070.431242
median451058.3453247.02188.70.485237
min236238.67236238.6718750.0018750.000001
std244197.003014247711.9218753514.9188611.439378
startNone2025-04-29T22:20:27.526833+00:00NaNNaN
endNone2025-04-30T22:20:27.526833+00:00NaNNaN

Configure Assays

Before creating the assay, configure the assay and continue to preview it until the best method for detecting drift is set. The following options are available.

Inference Interval and Inference Width

The inference interval aka window interval sets how often to run the assay analysis. This is set from the wallaroo.assay_config.AssayBuilder.window_builder.add_interval method to collect data expressed in time units: “hours=24”, “minutes=1”, etc.

For example, with an interval of 1 minute, the assay collects data every minute. Within an hour, 60 intervals of data is collected.

We can adjust the interval and see how the assays change based on how frequently they are run.

The width sets the time period from the wallaroo.assay_config.AssayBuilder.window_builder.add_width method to collect data expressed in time units: “hours=24”, “minutes=1”, etc.

For example, an interval of 1 minute and a width of 1 minute collects 1 minutes worth of data every minute. An interval of 1 minute with a width of 5 minutes collects 5 minute of inference data every minute.

By default, the interval and width is 24 hours.

For this example, we’ll adjust the width and interval from 24 hours to 12 hours.

The following images below demonstrate this change with assay analyses built on a longer historical period. Note that the tutorial code will only generate one data point - these images are for demonstration purposes.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show the analyses chart
assay_results_from_dates.chart_scores()
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=12).add_interval(hours=12).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show the analyses chart
assay_results_from_dates.chart_scores()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# create the baseline from the dates
assay_config_from_numpy = assay_baseline_from_numpy.build()

assay_results_from_numpy = assay_config_from_numpy.interactive_baseline_run()

assay_results_from_numpy.compare_basic_stats()
BaselineWindowdiffpct_diff
count5.000000e+025.000000e+020.00.0
min2.362387e+052.362387e+050.00.0
max2.005883e+062.005883e+060.00.0
mean5.359378e+055.359378e+050.00.0
median4.510583e+054.510583e+050.00.0
std2.441970e+052.441970e+050.00.0
startNaNNaNNaNNaN
endNaNNaNNaNNaN
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=12).add_interval(hours=12).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# create the baseline from the dates
assay_config_from_numpy = assay_baseline_from_numpy.build()

assay_results_from_numpy = assay_config_from_numpy.interactive_baseline_run()

assay_results_from_numpy.compare_basic_stats()
BaselineWindowdiffpct_diff
count5.000000e+025.000000e+020.00.0
min2.362387e+052.362387e+050.00.0
max2.005883e+062.005883e+060.00.0
mean5.359378e+055.359378e+050.00.0
median4.510583e+054.510583e+050.00.0
std2.441970e+052.441970e+050.00.0
startNaNNaNNaNNaN
endNaNNaNNaNNaN
Add Run Until and Add Inference Start

For previewing assays, setting wallaroo.assay_config.AssayBuilder.add_run_until sets the end date and time for collecting inference data. When an assay is uploaded, this setting is no longer valid - assays run at the Inference Interval until the assay is paused.

Setting the wallaroo.assay_config.WindowBuilder.add_start sets the start date and time to collect inference data. When an assay is uploaded, this setting is included, and assay results will be displayed starting from that start date at the Inference Interval until the assay is paused. By default, add_start begins 24 hours after the assay is uploaded unless set in the assay configuration manually.

For the following example, the add_start setting is set to datetime.datetime.now() to collect all inference data from 48 hours ago, then 24 hours ago.

The following images demonstrate assay analyses with historical data with the start and add run until settings changed. Note that the tutorial code will only generate one data point - these images are for demonstration purposes.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=12).add_interval(hours=12).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

assay_results_from_dates.chart_scores()
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=12).add_interval(hours=12).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

assay_results_from_dates.chart_scores()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# create the baseline from the dates
assay_config_from_numpy = assay_baseline_from_numpy.build()

assay_results_from_numpy = assay_config_from_numpy.interactive_baseline_run()

assay_results_from_numpy.compare_basic_stats()
BaselineWindowdiffpct_diff
count5.000000e+025.000000e+020.00.0
min2.362387e+052.362387e+050.00.0
max2.005883e+062.005883e+060.00.0
mean5.359378e+055.359378e+050.00.0
median4.510583e+054.510583e+050.00.0
std2.441970e+052.441970e+050.00.0
startNaNNaNNaNNaN
endNaNNaNNaNNaN
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# create the baseline from the dates
assay_config_from_numpy = assay_baseline_from_numpy.build()

assay_results_from_numpy = assay_config_from_numpy.interactive_baseline_run()

assay_results_from_numpy.compare_basic_stats()
BaselineWindowdiffpct_diff
count5.000000e+025.000000e+020.00.0
min2.362387e+052.362387e+050.00.0
max2.005883e+062.005883e+060.00.0
mean5.359378e+055.359378e+050.00.0
median4.510583e+054.510583e+050.00.0
std2.441970e+052.441970e+050.00.0
startNaNNaNNaNNaN
endNaNNaNNaNNaN
Score Metric

The score is a distance between the baseline and the analysis window. The larger the score, the greater the difference between the baseline and the analysis window. The following methods are provided determining the score:

  • PSI (Default) - Population Stability Index (PSI).
  • MAXDIFF: Maximum difference between corresponding bins.
  • SUMDIFF: Mum of differences between corresponding bins.

The metric type used is updated with the wallaroo.assay_config.AssayBuilder.add_metric(metric: wallaroo.assay_config.Metric) method.

The following three charts use each of the metrics. Note how the scores change based on the score type used.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# set metric PSI mode
assay_baseline_from_dates.summarizer_builder.add_metric(wallaroo.assay_config.Metric.PSI)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display one analysis from the results
assay_results_from_dates[0].chart()
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# set metric MAXDIFF mode
assay_baseline_from_dates.summarizer_builder.add_metric(wallaroo.assay_config.Metric.MAXDIFF)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display one analysis from the results
assay_results_from_dates[0].chart()
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# set metric SUMDIFF mode
assay_baseline_from_dates.summarizer_builder.add_metric(wallaroo.assay_config.Metric.SUMDIFF)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# display one analysis from the results
assay_results_from_dates[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# set metric PSI mode
assay_baseline_from_numpy.summarizer_builder.add_metric(wallaroo.assay_config.Metric.PSI)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show the analysis chart from the numpy baseline based assay
assay_results_from_numpy[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# set metric MAXDIFF mode
assay_baseline_from_numpy.summarizer_builder.add_metric(wallaroo.assay_config.Metric.MAXDIFF)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show the analysis chart from the numpy baseline based assay
assay_results_from_numpy[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# set metric SUMDIFF mode
assay_baseline_from_numpy.summarizer_builder.add_metric(wallaroo.assay_config.Metric.SUMDIFF)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show the analysis chart from the numpy baseline based assay
assay_results_from_numpy[0].chart()
Alert Threshold

Assay alert thresholds are modified with the wallaroo.assay_config.AssayBuilder.add_alert_threshold(alert_threshold: float) method. By default alert thresholds are 0.1.

The following example updates the alert threshold to 0.5.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# set the alert threshold
assay_baseline_from_dates.add_alert_threshold(0.5)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show the analyses with the alert threshold
assay_results_from_dates.to_dataframe()
idassay_idassay_nameiopathpipeline_idpipeline_nameworkspace_idworkspace_namescorestartminmaxmeanmedianstdwarning_thresholdalert_thresholdstatus
0003ab3e96-c842-43c6-add4-9b172e5c3b21assay-demonstration-tutorial assayout.variable.01assay-demonstration-tutorial7assay-demonstration-tutorial0.0127162025-04-29 22:20:27.526833+00:00236238.6718752005883.125538249.025087453247.0247711.921875None0.5Ok
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# set the alert threshold
assay_baseline_from_numpy.add_alert_threshold(0.5)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show the analyses with the alert threshold
assay_results_from_numpy.to_dataframe()
idassay_idassay_nameiopathpipeline_idpipeline_nameworkspace_idworkspace_namescorestartminmaxmeanmedianstdwarning_thresholdalert_thresholdstatus
0032cf10df-6c85-4131-9148-ffe21c310f94assay-demonstration-tutorial assayout.variable.01assay-demonstration-tutorial7assay-demonstration-tutorial0.0102072025-04-29 22:20:27.526833+00:00236238.6718752005883.125538249.025087453247.0247711.921875None0.5Ok
Number of Bins

Number of bins sets how the baseline data is partitioned. The total number of bins includes the set number plus the left_outlier and the right_outlier, so the total number of bins will be the total set + 2.

The number of bins is set with the wallaroo.assay_config.UnivariateContinousSummarizerBuilder.add_num_bins(num_bins: int) method.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# update number of bins here
assay_baseline_from_dates.summarizer_builder.add_num_bins(10)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show one analysis with the updated bins
assay_results_from_dates[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# update number of bins here
assay_baseline_from_numpy.summarizer_builder.add_num_bins(10)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show one analysis with the updated bins
assay_results_from_numpy[0].chart()
Binning Mode

Binning Mode defines how the bins are separated. Binning modes are modified through the wallaroo.assay_config.UnivariateContinousSummarizerBuilder.add_bin_mode(bin_mode: bin_mode: wallaroo.assay_config.BinMode, edges: Optional[List[float]] = None).

Available bin_mode values from wallaroo.assay_config.Binmode are the following:

  • QUANTILE (Default): Based on percentages. If num_bins is 5 then quintiles so bins are created at the 20%, 40%, 60%, 80% and 100% points.
  • EQUAL: Evenly spaced bins where each bin is set with the formula min - max / num_bins
  • PROVIDED: The user provides the edge points for the bins.

If PROVIDED is supplied, then a List of float values must be provided for the edges parameter that matches the number of bins.

The following examples are used to show how each of the binning modes effects the bins.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# update binning mode here
assay_baseline_from_dates.summarizer_builder.add_bin_mode(wallaroo.assay_config.BinMode.QUANTILE)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show one analysis with the updated bins
assay_results_from_dates[0].chart()
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# # Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# # Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

# # update binning mode here
assay_baseline_from_dates.summarizer_builder.add_bin_mode(wallaroo.assay_config.BinMode.EQUAL)

# # build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# # perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# # show one analysis with the updated bins
assay_results_from_dates[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# update binning mode here
assay_baseline_from_numpy.summarizer_builder.add_bin_mode(wallaroo.assay_config.BinMode.QUANTILE)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show one analysis with the updated bins
assay_results_from_numpy[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# update binning mode here
assay_baseline_from_numpy.summarizer_builder.add_bin_mode(wallaroo.assay_config.BinMode.EQUAL)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show one analysis with the updated bins
assay_results_from_numpy[0].chart()

The following example manually sets the bin values.

The values in this dataset run from 200000 to 1500000. We can specify the bins with the BinMode.PROVIDED and specifying a list of floats with the right hand / upper edge of each bin and optionally the lower edge of the smallest bin. If the lowest edge is not specified the threshold for left outliers is taken from the smallest value in the baseline dataset.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

edges = [200000.0, 400000.0, 600000.0, 800000.0, 1500000.0, 2000000.0]

# update binning mode here
assay_baseline_from_dates.summarizer_builder.add_bin_mode(wallaroo.assay_config.BinMode.PROVIDED, edges)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show one analysis with the updated bins
assay_results_from_dates[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

edges = [200000.0, 400000.0, 600000.0, 800000.0, 1500000.0, 2000000.0]

# update binning mode here
assay_baseline_from_numpy.summarizer_builder.add_bin_mode(wallaroo.assay_config.BinMode.PROVIDED, edges)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show one analysis with the updated bins
assay_results_from_numpy[0].chart()
Aggregation Options

Assay aggregation options are modified with the wallaroo.assay_config.AssayBuilder.add_aggregation(aggregation: wallaroo.assay_config.Aggregation) method. The following options are provided:

  • Aggregation.DENSITY (Default): Count the number/percentage of values that fall in each bin.
  • Aggregation.CUMULATIVE: Empirical Cumulative Density Function style, which keeps a cumulative count of the values/percentages that fall in each bin.

The following example demonstrate the different results between the two.

assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

#Aggregation.DENSITY - the default
assay_baseline_from_dates.summarizer_builder.add_aggregation(wallaroo.assay_config.Aggregation.DENSITY)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show one analysis with the updated bins
assay_results_from_dates[0].chart()
assay_baseline_from_dates = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_dates.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_dates.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-48))

#Aggregation.CUMULATIVE - the default
assay_baseline_from_dates.summarizer_builder.add_aggregation(wallaroo.assay_config.Aggregation.CUMULATIVE)

# build the assay configuration
assay_config_from_dates = assay_baseline_from_dates.build()

# perform an interactive run and collect inference data
assay_results_from_dates = assay_config_from_dates.interactive_run()

# show one analysis with the updated bins
assay_results_from_dates[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# Aggregation.DENSITY - the default
assay_baseline_from_numpy.summarizer_builder.add_aggregation(wallaroo.assay_config.Aggregation.DENSITY)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show one analysis with the updated bins
assay_results_from_numpy[0].chart()
assay_baseline_from_numpy = wl.build_assay(assay_name=assay_name, 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_data=baseline_results)

# Set the assay parameters

# The end date to gather inference results
assay_baseline_from_numpy.add_run_until(assay_baseline_end)

# Set the interval and window to one minute each, set the start date back 24 hours for gathering inference results
assay_baseline_from_numpy.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# Aggregation.CUMULATIVE - the default
assay_baseline_from_numpy.summarizer_builder.add_aggregation(wallaroo.assay_config.Aggregation.CUMULATIVE)

# build the assay configuration
assay_config_from_numpy = assay_baseline_from_numpy.build()

# perform an interactive run and collect inference data
assay_results_from_numpy = assay_config_from_numpy.interactive_run()

# show one analysis with the updated bins
assay_results_from_numpy[0].chart()

Create Assay

With the assay previewed and configuration options determined, we officially create it by uploading it to the Wallaroo instance.

Once it is uploaded, the assay runs an analysis based on the window width, interval, and the other settings configured.

Assays are uploaded with the wallaroo.assay_config.upload() method. This uploads the assay into the Wallaroo database with the configurations applied and returns the assay id. Note that assay names must be unique across the Wallaroo instance; attempting to upload an assay with the same name as an existing one will return an error.

wallaroo.assay_config.upload() returns the assay id for the assay.

Typically we would just call wallaroo.assay_config.upload() after configuring the assay. For the example below, we will perform the complete configuration in one window to show all of the configuration steps at once before creating the assay.

# Build the assay, based on the start and end of our baseline time, 
# and tracking the output variable index 0
assay_baseline = wl.build_assay(assay_name="assays from date baseline tutorial samples", 
                                          pipeline=mainpipeline, 
                                          iopath="output variable 0",
                                          baseline_start=assay_baseline_start, 
                                          baseline_end=assay_baseline_end)

# Set the interval and window to one minute each, set the start date for gathering inference results
assay_baseline.window_builder().add_width(hours=24).add_interval(hours=24).add_start(assay_baseline_start+datetime.timedelta(hours=-24))

# add other options
assay_baseline.summarizer_builder.add_aggregation(wallaroo.assay_config.Aggregation.CUMULATIVE)
assay_baseline.summarizer_builder.add_metric(wallaroo.assay_config.Metric.MAXDIFF)
assay_baseline.add_alert_threshold(0.5)

assay_id = assay_baseline.upload()

# wait 65 seconds for the first analysis run performed
time.sleep(65)
# sample inference to give the assay analysis inference data to work with
baseline_inference_results = mainpipeline.infer(baseline_sample)

The assay is now visible through the Wallaroo UI by selecting the workspace, then the pipeline, then Insights.

Get Assay Info

Assay information is retrieved with the wallaroo.client.get_assay_info() which takes the following parameters.

ParameterTypeDescription
assay_idString (Required)The numerical id of the assay in UUID format.
workspace_id(Int) (Optional)The numerical identifier of the workspace to filter by.
workspace_name(String) (Optional)The name of the workspace to filter by.

This returns the following:

ParameterTypeDescription
idStringThe id of the assay in UUID format.
nameStringThe name of the assay.
activeBooleanTrue: The assay is active and generates analyses based on its configuration. False: The assay is disabled and will not generate new analyses.
pipeline_nameStringThe name of the pipeline the assay references.
last_runDateTimeThe date and time the assay last ran.
next_runDateTimeTHe date and time the assay analysis will next run.
alert_thresholdFloatThe alert threshold setting for the assay.
baselineDictThe baseline and settings as set from the assay configuration.
iopathStringThe iopath setting for the assay.
metricStringThe metric setting for the assay.
num_binsIntegerThe number of bins for the assay.
bin_weightsList/NoneThe bin weights used if any.
bin_modeStringThe binning mode used.
workspace_id(Int)The numerical identifier of the workspace the assay is associated with.
workspace_name(String)The name of the workspace the assay is associated with.

Get Assay Results

Once an assay is created the assay runs an analysis based on the window width, interval, and the other settings configured.

Assay results are retrieved with the wallaroo.client.get_assay_results method which takes the following parameters:

ParameterTypeDescription
assay_idString (Required)The id of the assay in UUID format.
startDatetime.Datetime (Required)The start date and time of historical data from the pipeline to start analyses from.
endDatetime.Datetime (Required)The end date and time of historical data from the pipeline to limit analyses to.
workspace_id(Int) (Optional)The numerical identifier of the workspace to filter by.
workspace_name(String) (Optional)The name of the workspace to filter by.
  • IMPORTANT NOTE: This process requires that additional historical data is generated from the time the assay is created to when the results are available. To add additional inference data, use the Assay Test Data section above.
assay_results = wl.get_assay_results(assay_id,
                     start = assay_baseline_start+datetime.timedelta(hours=-24),
                     end = assay_baseline_end)
assay_results[0].chart()

List Assays

A list of assays is retrieved with the wallaroo.client.list_assays() method and takes the following parameters:

ParameterTypeDescription
workspace_id(Int) (Optional)The numerical identifier of the workspace to filter by.
workspace_name(String) (Optional)The name of the workspace to filter by.

This returns a list of assays as filtered in reverse chronological order.

ParameterTypeDescription
Assay IdStringThe id of the assay in UUID format.
Assay NameStringThe name of the assay.
ActiveBooleanTrue: The assay is active and generates analyses based on its configuration. False: The assay is disabled and will not generate new analyses.
StatusDictThe status of the assay including the
Warning ThresholdFloat/NoneThe warning threshold if set.
Alert ThresholdFloatThe alert threshold for the assay.
workspace_id(Int)The numerical identifier of the workspace the assay is associated with.
workspace_name(String)The name of the workspace the assay is associated with.

The errors for this method include:

  • If the parameter workspace_id is not an integer.
  • If the parameter workspace_name is not a String.
wl.list_assays()
idnameactivepipelineworkspace idworkspace namemonitored fieldslast_runnext_runcreated_atupdated_at
79beecf1-a9ce-41e0-b1e6-da418f3c0aa2assays from date baseline tutorial samplesTrueassay-demonstration-tutorial7assay-demonstration-tutorial['out.variable.0']2025-30-Apr 22:20:272025-01-May 22:20:272025-30-Apr 22:55:432025-30-Apr 22:55:43

Set Assay Active Status

Assays active status is either:

  • True: The assay generates analyses based on the assay configuration.
  • False: The assay will not generate new analyses.

Assays are set to active or not active with the wallaroo.client.set_assay_active which takes the following parameters.

ParameterTypeDescription
assay_idStringThe id of the assay in UUID format.
activeBooleanTrue: The assay status is set to Active. False: The assay status is Not Active.

First we will show the current active status.

In the following, set the assay status to False, then set the assay active status back to True.

display(wl.get_assay_info(assay_id))
FieldValue
ID79beecf1-a9ce-41e0-b1e6-da418f3c0aa2
Nameassays from date baseline tutorial samples
ActiveTrue
Pipelineassay-demonstration-tutorial
Workspace ID7
Workspace Nameassay-demonstration-tutorial
Monitoring['out.variable.0']
Window Width86400 seconds
First Run2025-30-Apr 22:20:27
Run Frequency24 Hour
Bin Mode5 Quantile bins
AggregationCumulative
MetricMaxDiff
Last Run2025-30-Apr 22:20:27
Next Run2025-01-May 22:20:27
Created At2025-30-Apr 22:55:43
Updated At2025-30-Apr 22:55:43

Now we set the active status to False, and show the assay list to verify it is no longer active.

wl.set_assay_active(assay_id, False)
display(wl.get_assay_info(assay_id))
FieldValue
ID79beecf1-a9ce-41e0-b1e6-da418f3c0aa2
Nameassays from date baseline tutorial samples
ActiveFalse
Pipelineassay-demonstration-tutorial
Workspace ID7
Workspace Nameassay-demonstration-tutorial
Monitoring['out.variable.0']
Window Width86400 seconds
First Run2025-30-Apr 22:20:27
Run Frequency24 Hour
Bin Mode5 Quantile bins
AggregationCumulative
MetricMaxDiff
Last Run2025-30-Apr 22:20:27
Next RunNone
Created At2025-30-Apr 22:55:43
Updated At2025-30-Apr 22:57:04

We resume the assay by setting it’s active status to True.

wl.set_assay_active(assay_id, True)
display(wl.get_assay_info(assay_id))
# setting to false to preserve resources
wl.set_assay_active(assay_id, False)
FieldValue
ID79beecf1-a9ce-41e0-b1e6-da418f3c0aa2
Nameassays from date baseline tutorial samples
ActiveTrue
Pipelineassay-demonstration-tutorial
Workspace ID7
Workspace Nameassay-demonstration-tutorial
Monitoring['out.variable.0']
Window Width86400 seconds
First Run2025-30-Apr 22:20:27
Run Frequency24 Hour
Bin Mode5 Quantile bins
AggregationCumulative
MetricMaxDiff
Last Run2025-30-Apr 22:20:27
Next Run2025-01-May 22:20:27
Created At2025-30-Apr 22:55:43
Updated At2025-30-Apr 22:57:06

Undeploy Main Pipeline

With the examples and tutorial complete, we will undeploy the main pipeline and return the resources back to the Wallaroo instance.

mainpipeline.undeploy()
nameassay-demonstration-tutorial
created2025-04-09 16:06:07.269533+00:00
last_updated2025-04-30 21:47:57.083176+00:00
deployedFalse
workspace_id7
workspace_nameassay-demonstration-tutorial
archx86
accelnone
tags
versionsf6f61d02-c0dd-4f06-8765-d4d1a04301fb, 53bd5246-cce7-492e-add4-337e639c9176, d356bf98-3d55-4b96-b1fe-da2044ce4dd8, 2b0eb1a9-33b6-4403-aa68-f34ba072163d, ddae3a00-13c8-469b-bda7-6a25ae089166, 64fe4837-7311-4651-b8f9-67cbfdc8a70f, 9ffe4dc5-4078-4864-b6f1-6dd3fe4f1429
stepshouse-price-estimator
publishedFalse

View Assay Filters

The following examples demonstrate various ways of listing assays and retrieving assay details, with filtering options.

List Assays with Filters

List all assays for workspaces the user is a member of.

wl.list_assays()
idnameactivepipelineworkspace idworkspace namemonitored fieldslast_runnext_runcreated_atupdated_at
79beecf1-a9ce-41e0-b1e6-da418f3c0aa2assays from date baseline tutorial samplesFalseassay-demonstration-tutorial7assay-demonstration-tutorial['out.variable.0']2025-30-Apr 22:20:27None2025-30-Apr 22:55:432025-30-Apr 22:57:07

List all assays filtered by workspace id.

wl.list_assays(workspace_id=workspace.id())
idnameactivepipelineworkspace idworkspace namemonitored fieldslast_runnext_runcreated_atupdated_at
79beecf1-a9ce-41e0-b1e6-da418f3c0aa2assays from date baseline tutorial samplesFalseassay-demonstration-tutorial7assay-demonstration-tutorial['out.variable.0']2025-30-Apr 22:20:27None2025-30-Apr 22:55:432025-30-Apr 22:57:07

List assays filtered by workspace name.

wl.list_assays(workspace_name=workspace_name)
idnameactivepipelineworkspace idworkspace namemonitored fieldslast_runnext_runcreated_atupdated_at
79beecf1-a9ce-41e0-b1e6-da418f3c0aa2assays from date baseline tutorial samplesFalseassay-demonstration-tutorial7assay-demonstration-tutorial['out.variable.0']2025-30-Apr 22:20:27None2025-30-Apr 22:55:432025-30-Apr 22:57:07

Get Assay Results with Filters

Get assay results.

assay_results = wl.get_assay_results(assay_id=assay_id,
                     start=assay_baseline_start,
                     end=datetime.datetime.now())

assay_results.chart_scores()

Get assay results filtered by workspace id.

assay_results = wl.get_assay_results(assay_id=assay_id,
                                     workspace_id=workspace.id(),
                                     start=assay_baseline_start+datetime.timedelta(hours=-24),
                                     end=datetime.datetime.now())

assay_results.chart_scores()

Get assay results filtered by workspace name.

assay_results = wl.get_assay_results(assay_id=assay_id,
                                     workspace_name=workspace_name,
                     start=assay_baseline_start,
                     end=datetime.datetime.now())

assay_results.chart_scores()

Get Assay Info with Filter

Get assay info.

wl.get_assay_info(assay_id=assay_id)
FieldValue
ID79beecf1-a9ce-41e0-b1e6-da418f3c0aa2
Nameassays from date baseline tutorial samples
ActiveFalse
Pipelineassay-demonstration-tutorial
Workspace ID7
Workspace Nameassay-demonstration-tutorial
Monitoring['out.variable.0']
Window Width86400 seconds
First Run2025-30-Apr 22:20:27
Run Frequency24 Hour
Bin Mode5 Quantile bins
AggregationCumulative
MetricMaxDiff
Last Run2025-30-Apr 22:20:27
Next RunNone
Created At2025-30-Apr 22:55:43
Updated At2025-30-Apr 22:57:07

Get assay info filtered by workspace id.

wl.get_assay_info(assay_id=assay_id, 
                 workspace_id=workspace.id())
FieldValue
ID79beecf1-a9ce-41e0-b1e6-da418f3c0aa2
Nameassays from date baseline tutorial samples
ActiveFalse
Pipelineassay-demonstration-tutorial
Workspace ID7
Workspace Nameassay-demonstration-tutorial
Monitoring['out.variable.0']
Window Width86400 seconds
First Run2025-30-Apr 22:20:27
Run Frequency24 Hour
Bin Mode5 Quantile bins
AggregationCumulative
MetricMaxDiff
Last Run2025-30-Apr 22:20:27
Next RunNone
Created At2025-30-Apr 22:55:43
Updated At2025-30-Apr 22:57:07

Get assay info filtered by workspace name.

wl.get_assay_info(assay_id=assay_id, 
                 workspace_name=workspace_name)
FieldValue
ID79beecf1-a9ce-41e0-b1e6-da418f3c0aa2
Nameassays from date baseline tutorial samples
ActiveFalse
Pipelineassay-demonstration-tutorial
Workspace ID7
Workspace Nameassay-demonstration-tutorial
Monitoring['out.variable.0']
Window Width86400 seconds
First Run2025-30-Apr 22:20:27
Run Frequency24 Hour
Bin Mode5 Quantile bins
AggregationCumulative
MetricMaxDiff
Last Run2025-30-Apr 22:20:27
Next RunNone
Created At2025-30-Apr 22:55:43
Updated At2025-30-Apr 22:57:07