Testland
Browse all skills & agents

notebook-ci-pipeline-author

The single home for Jupyter notebook testing: wires parameterized execution (papermill), output regression (nbval), function-level unit tests (testbook), output stripping (nbstripout), and artifact upload into one working GitHub Actions CI pipeline, with per-tool depth for papermill (parameters tag, CLI/API, sweeps) and nbval (strict/lax modes, per-cell markers, sanitize config) in references/. Includes a notebook PR review checklist covering untested notebooks, --nbval-lax misuse, hardcoded credentials, non-deterministic output cells, missing parameters tags, and committed outputs, with BLOCK / WARN / INFO severities and a BLOCK-or-PASS verdict. Use when notebooks must run as parameterized regression jobs in CI, when a repo ships .ipynb files whose outputs must stay stable, or when a PR that adds or modifies notebooks needs a structured quality review.

Install with skills.sh (any agent)

npx skills add testland/qa --skill notebook-ci-pipeline-author
View source

notebook-ci-pipeline-author

Composes the three notebook testing tools into one GitHub Actions pipeline: papermill executes parameterized notebooks, nbval validates output regression, testbook runs function-level unit tests, and nbstripout gates committed output. Per-tool depth lives in references/papermill.md (parameterized execution) and references/nbval.md (output regression); this SKILL.md covers the wiring and integration decisions, plus the PR review checklist for notebook changes.

When to use

Teams using all three tools but assembling the pipeline by hand: no consistent artifact naming, no shared caching, duplicate install steps, no HTML report on failure. Also: any PR that adds or modifies .ipynb files and needs the review checklist below.

Hard-reject conditions

Do not proceed if any of the following apply:

  • The repository has no parameters-tagged notebook cell. Papermill injection silently uses notebook defaults when the tag is absent per the Papermill execute docs (opens in new window); the pipeline will report green against stale values.
  • nbval ground-truth outputs are absent from the committed .ipynb files. Re-running cells with no stored outputs produces no diff, so regressions are invisible.
  • testbook tests use execute=True per-function without a scope="module" fixture. Each test re-executes the kernel; CI timeouts follow per the testbook docs (opens in new window).

State the blocker to the user and stop.

Step 1 - Install nbstripout as a pre-commit filter

Install once per clone so committed notebooks carry no output noise per the nbstripout README (opens in new window):

pip install nbstripout
nbstripout --install                    # writes .git/config filter entry
nbstripout --install --attributes .gitattributes  # repo-wide via .gitattributes

Add to .gitattributes:

*.ipynb filter=nbstripout

For pull-request verification without modifying files, use the kynan/nbstripout action (opens in new window):

- name: Verify notebooks are stripped
  uses: kynan/nbstripout@main
  with:
    paths: '**/*.ipynb'

The action runs a dry-run check and fails if any notebook carries uncommitted output.

Step 2 - Install dependencies with pip caching

Per GitHub Actions: Building and Testing Python (opens in new window), the setup-python action accepts cache: 'pip' and locates requirements.txt automatically:

- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'

- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
    pip install papermill nbval pytest testbook nbconvert

Keep papermill nbval pytest testbook nbconvert pinned in requirements.txt so the cache key (hashFiles('**/requirements.txt')) reflects version changes.

Step 3 - Stage 1: papermill parameterized execution

Papermill executes the notebook with injected parameters and writes a fully-rendered output notebook:

- name: Execute notebook (papermill)
  run: |
    papermill notebooks/analysis.ipynb \
      artifacts/analysis-executed.ipynb \
      -p seed 42 \
      -p n_samples 1000

Use -p for numeric/boolean parameters and -r for string parameters to prevent type-coercion surprises per the Papermill execute docs (opens in new window). Full papermill depth - parameter flags, Python API, matrix sweeps, regression-test wiring - is in references/papermill.md. Store the output path (artifacts/analysis-executed.ipynb) in an env var shared across stages:

env:
  EXECUTED_NB: artifacts/analysis-executed.ipynb

Step 4 - Stage 2: nbval output regression

Run nbval in lax mode on the executed notebook. Strict mode fails on every non-deterministic output; lax mode fails only on errors unless cells carry #NBVAL_CHECK_OUTPUT per the nbval docs (opens in new window):

- name: Output regression (nbval-lax)
  run: |
    pytest --nbval-lax $EXECUTED_NB \
      --sanitize-with sanitize.cfg \
      -v

sanitize.cfg example for timestamps and memory addresses:

[regex1]
regex: \d{1,2}/\d{1,2}/\d{2,4}
replace: DATE-STAMP

[regex2]
regex: 0x[0-9a-fA-F]+
replace: MEMORY-ADDR

Pin per-cell markers on cells that emit timestamps or large floats: # NBVAL_IGNORE_OUTPUT. Use # NBVAL_RAISES_EXCEPTION to validate expected error paths. Full nbval depth - strict vs lax mode, all per-cell controls, sanitize patterns, discovery - is in references/nbval.md.

Step 5 - Stage 3: testbook function unit tests

Run testbook tests against the source notebook (not the executed artifact) using a module-scoped fixture so the kernel executes once per pytest session per the testbook docs (opens in new window):

- name: Unit tests (testbook)
  run: pytest tests/test_notebook_functions.py -v

The scope="module" fixture is the load-bearing wiring decision - it stops each test re-executing the kernel:

@pytest.fixture(scope="module")
def tb():
    with testbook("notebooks/analysis.ipynb", execute=True) as tb:
        yield tb

The full tests/test_notebook_functions.py, with per-function tb.ref() assertions, is in references/notebook-ci-pipeline.md.

Step 6 - Stage 4: HTML report via nbconvert

Convert the executed notebook to a self-contained HTML report per the nbconvert docs (opens in new window):

- name: Convert to HTML
  if: always()
  run: |
    jupyter nbconvert --to html \
      --template lab \
      --embed-images \
      $EXECUTED_NB \
      --output artifacts/analysis-report.html

if: always() per GitHub Actions expressions (opens in new window) ensures the report generates even when nbval or testbook failed; the HTML is the primary debugging artifact.

Step 7 - Artifact upload with failure-aware retention

Upload both the executed notebook and the HTML report. Use if: always() so artifacts surface on failure per actions/upload-artifact@v4 (opens in new window):

- name: Upload artifacts
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: notebook-ci-${{ github.run_id }}
    path: |
      artifacts/analysis-executed.ipynb
      artifacts/analysis-report.html
    if-no-files-found: warn
    retention-days: 14

Set retention-days within the 1-90 day range allowed by actions/upload-artifact@v4 (opens in new window); 14 days covers sprint cycles without excessive storage.

Step 8 - Complete workflow

Steps 1-7 assemble into one workflow file. The full assembled YAML is in references/notebook-ci-pipeline.md; paste it to .github/workflows/notebook-ci.yml and adjust the notebook path, papermill parameters, and test path to match the repo.

Step 9 - Review checklist for notebook PRs

When a PR adds or modifies .ipynb files, walk each notebook through six checks and emit a finding table with a verdict. Every finding must trace to an observable file pattern or a cited source, never intuition.

  1. Untested notebooks. Cross-reference conftest.py, pytest.ini, CI workflow YAML, and tests/. Each notebook needs at least one of: an --nbval / --nbval-lax CI step, a testbook test file referencing it, or a papermill execution step. No coverage = UNTESTED.
  2. --nbval-lax misuse. --nbval-lax "collects notebooks and runs them, failing if there is an error" but skips output comparison unless cells carry #NBVAL_CHECK_OUTPUT (nbval docs (opens in new window)). Flag when the notebook is a regression test (its purpose is validating outputs) AND no cell carries the marker; fix is strict --nbval or adding markers to load-bearing cells.
  3. Hardcoded credentials. Scan source cells for API-key assignments (api_key =, token =, password =, secret =), connection strings (://user:pass@), and long base64 blobs in string literals - and scan cell outputs too (secrets echoed in stream / execute_result blocks are equally dangerous and often missed). A match is a match; do not judge intent.
  4. Non-deterministic output cells. Stored outputs containing timestamps, memory addresses, UUIDs, or floats with > 4 significant figures, with no #NBVAL_IGNORE_OUTPUT marker and no --sanitize-with config in CI, produce spurious diffs or false failures.
  5. parameters tag. For papermill-executed notebooks, exactly one cell must carry the parameters tag; with no tag, papermill inserts injected-parameters at the top, potentially before imports (Papermill parameterize docs (opens in new window)). Flag zero tags (MISSING-PARAMS-TAG) and multiple tags (AMBIGUOUS-PARAMS-TAG).
  6. Committed outputs. Any code cell with a non-empty outputs array, in a repo without an nbstripout hook, bloats diffs and causes merge conflicts per nbstripout README (opens in new window) - flag COMMITTED-OUTPUTS. With the hook configured, the flag means the PR bypassed it.

Severity: BLOCK = credentials, untested; WARN = lax misuse, committed outputs, non-deterministic outputs; INFO = params-tag findings. Verdict is BLOCK if any BLOCK-severity finding is present, PASS otherwise.

Anti-patterns

Anti-patternWhy it failsFix
Run nbval on the source notebook before papermillnbval re-executes from scratch; parameter injection never happensRun nbval on the papermill output notebook (Stage 2)
Run testbook tests against the executed artifacttestbook needs the source notebook to resolve cell tags; .ipynb with injected-parameters cell confuses selective executionPoint testbook at the source notebook, not the artifact
Omit nbstripout --install from onboardingDevelopers commit outputs; nbval diffs against stale ground truth in CIDocument nbstripout --install in CONTRIBUTING.md; enforce via the kynan/nbstripout action (Step 1)
Upload artifacts only on successFailures produce no HTML; engineers cannot inspect which cell erroredUse if: always() on the convert and upload steps (Steps 6-7)
Module-scope fixture missing from testbook testsEach test re-executes the full notebook kernel; multi-minute CI runs per testAdd @pytest.fixture(scope="module") (Step 5)

Limitations

  • Papermill, nbval, and testbook each spawn a separate kernel session. For very large notebooks (> 5 min execution), total CI time triples. Use selective cell execution in testbook (pass a list of tags to execute) and split long notebooks into composable units.
  • nbval and testbook conflict when run against the same notebook in the same pytest session per the testbook docs (opens in new window). This workflow avoids the conflict by pointing each tool at a different target (nbval on the executed artifact, testbook on the source).
  • The kynan/nbstripout action checks output presence but does not enforce kernel metadata stripping; add --extra-keys flags locally if kernel version metadata causes diff noise.

References

nbval - notebook output regression

View source (opens in new window)

nbval - notebook output regression

The output-regression stage of the pipeline (SKILL.md Step 4) in tool depth: strict vs lax mode, per-cell controls, sanitize config, and discovery.

nbval is a pytest plugin that validates Jupyter notebooks by re-executing cells and comparing outputs against stored results, "ensuring that the notebook is behaving as expected and that changes to underlying source code haven't affected the results" per the nbval docs (opens in new window).

When to use

  • A team ships analyses, tutorials, or docs as notebooks - protect them from silent rot when libraries upgrade.
  • Notebooks that double as integration tests for a Python library (the README example actually executes).
  • CI gate: every PR re-runs the notebook corpus.

Step 1 - Install

pip install nbval pytest

Per the nbval docs (opens in new window).

Step 2 - Strict mode (default)

pytest --nbval my_notebook.ipynb

Re-executes every cell; fails if any output differs from stored.

Step 3 - Lax mode (failure-only)

pytest --nbval-lax my_notebook.ipynb

"Collects notebooks and runs them, failing if there is an error" - skips output comparison unless cells bear the #NBVAL_CHECK_OUTPUT marker per the nbval docs (opens in new window). Use as the default for tutorials where output is incidental and execution is what matters.

Step 4 - Per-cell controls

Add comments at cell start:

MarkerEffect
# NBVAL_SKIPCell not executed during testing
# NBVAL_IGNORE_OUTPUTCell runs; output diff ignored
# NBVAL_CHECK_OUTPUTForce output checking (lax mode)
# NBVAL_RAISES_EXCEPTIONValidate that the cell raises

Cell tags (in notebook metadata, lowercase-with-dashes: nbval-skip, nbval-ignore-output, etc.) are equivalent and recommended for non-Python kernels.

Step 5 - Sanitize dynamic outputs

For timestamps, UUIDs, memory addresses:

pytest --nbval my_notebook.ipynb --sanitize-with sanitize.cfg

sanitize.cfg:

[regex1]
regex: \d{1,2}/\d{1,2}/\d{2,4}
replace: DATE-STAMP

[regex2]
regex: 0x[0-9a-fA-F]+
replace: MEMORY-ADDR

[regex3]
regex: \d+\.\d+(?:e-?\d+)?
replace: NUMBER

Tune carefully - over-sanitizing makes nbval miss real regressions.

Step 6 - Test discovery

# Whole notebooks/ directory
pytest --nbval notebooks/

# Filter by name
pytest --nbval notebooks/ -k "tutorial"

# Single notebook + verbose
pytest --nbval --verbose notebooks/intro.ipynb

Step 7 - CI integration

# GitHub Actions
- name: Set up Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.11'

- name: Install
  run: |
    pip install -r requirements.txt
    pip install nbval pytest

- name: Run notebook tests (lax)
  run: pytest --nbval-lax notebooks/ --sanitize-with sanitize.cfg

For tutorial repos, lax mode + sanitize is usually right.

Anti-patterns

Anti-patternWhy it failsFix
Use strict mode for tutorial notebooksEvery random seed change fails CIUse --nbval-lax (Step 3)
Skip cells liberally with # NBVAL_SKIPCoverage shrinks; notebook becomes untestedUse # NBVAL_IGNORE_OUTPUT instead - still verifies execution
Sanitize all numeric outputReal regressions hiddenTargeted regexes (Step 5)
Run nbval against notebooks that mutate disk/stateTests become flakyUse ephemeral working dirs; monkeypatch.chdir(tmp_path)
No requirements.txt pinning"Works on author's machine"; CI fails on minor lib bumpsPin notebook deps separately from prod deps

Limitations

  • Stored output ground truth lives in the .ipynb file - git diffs for output cells are noisy. Use nbstripout only when sanitize
    • lax mode aren't enough.
  • Non-Python kernels (R, Julia) require kernel install on CI.
  • Long-running cells slow CI; consider running notebook tests in a separate workflow with caching.

References

notebook-ci-pipeline - reference bundle

View source (opens in new window)

notebook-ci-pipeline - reference bundle

Deep detail for notebook-ci-pipeline-author: the full assembled GitHub Actions workflow and the complete testbook test file. The SKILL.md spine holds the per-stage snippets and integration decisions; this bundle holds the two longest blocks so the spine stays focused.

Complete workflow

Steps 1-7 of the skill assemble into one workflow file. Paste this to .github/workflows/notebook-ci.yml and adjust the notebook path, papermill parameters, and test path to match the repo. Order matters: nbstripout verify -> pip cache -> papermill -> nbval-lax -> testbook -> nbconvert HTML -> artifact upload.

name: Notebook CI

on:
  push:
    paths:
      - 'notebooks/**'
      - 'tests/**'
      - 'requirements.txt'
  pull_request:
    paths:
      - 'notebooks/**'

jobs:
  notebook-ci:
    runs-on: ubuntu-latest
    env:
      EXECUTED_NB: artifacts/analysis-executed.ipynb

    steps:
      - uses: actions/checkout@v4

      - name: Verify notebooks are stripped
        uses: kynan/nbstripout@main
        with:
          paths: '**/*.ipynb'

      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt

      - name: Execute notebook (papermill)
        run: |
          mkdir -p artifacts
          papermill notebooks/analysis.ipynb \
            $EXECUTED_NB \
            -p seed 42 \
            -p n_samples 1000

      - name: Output regression (nbval-lax)
        run: |
          pytest --nbval-lax $EXECUTED_NB \
            --sanitize-with sanitize.cfg \
            -v

      - name: Unit tests (testbook)
        run: pytest tests/test_notebook_functions.py -v

      - name: Convert to HTML
        if: always()
        run: |
          jupyter nbconvert --to html \
            --template lab \
            --embed-images \
            $EXECUTED_NB \
            --output artifacts/analysis-report.html

      - name: Upload artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: notebook-ci-${{ github.run_id }}
          path: |
            artifacts/analysis-executed.ipynb
            artifacts/analysis-report.html
          if-no-files-found: warn
          retention-days: 14

Full testbook test file

tests/test_notebook_functions.py - the module-scoped fixture executes the kernel once per pytest session; each test resolves a notebook function with tb.ref() and asserts on its return value:

import pytest
from testbook import testbook

@pytest.fixture(scope="module")
def tb():
    with testbook("notebooks/analysis.ipynb", execute=True) as tb:
        yield tb

def test_clean_data_drops_nulls(tb):
    clean_data = tb.ref("clean_data")
    result = clean_data(tb.ref("pd").DataFrame({"a": [1, None, 3]}))
    assert len(result) == 2

def test_model_output_shape(tb):
    predict = tb.ref("predict")
    assert predict(tb.ref("test_input")).shape == (1,)

Papermill - parameterized notebook execution

View source (opens in new window)

Papermill - parameterized notebook execution

The execution stage of the pipeline (SKILL.md Step 3) in tool depth: the parameters cell tag, CLI + Python API, parameter flags, sweeps, and regression-test wiring.

Papermill executes notebooks programmatically with injected parameters, producing an output notebook with results. Per the Papermill execute docs (opens in new window), it pairs naturally with regression testing: run a parameterized notebook in CI, assert on outputs.

When to use

  • Parameterized analysis notebooks: same notebook, different inputs (per-region, per-month, per-customer-segment).
  • Production-grade notebook execution (Airflow / Argo / Prefect / cron) - papermill is the standard executor.
  • Regression test: re-run the notebook with known inputs; assert output values match expected (often paired with nbval/testbook).

How to use

  1. pip install papermill (Step 1).
  2. Tag one notebook cell parameters with default values (Step 2).
  3. Execute with injected inputs via the Python API pm.execute_notebook(...) or the CLI papermill in.ipynb out.ipynb -p NAME VAL (Steps 3-4).
  4. Read the output notebook with nbformat and assert on the final cell's values inside a pytest test (Step 5).
  5. Always seed stochastic parameters (-p seed N) so reruns are reproducible.
  6. Upload the output notebook as a CI artifact so failed assertions stay inspectable (Step 6).
  7. Layer nbval / testbook on top of papermill's execution for output and function-level assertions (Step 7).

Step 1 - Install

pip install papermill

Per the Papermill execute docs (opens in new window).

Step 2 - Tag the parameters cell

In your notebook, tag one cell with parameters:

# Cell tagged "parameters"
alpha = 0.5
ratio = 0.2
input_path = "data/sales.parquet"

Papermill replaces these with injected values at execution time (adds an injected-parameters cell after the tagged cell).

Step 3 - Python API execution

import papermill as pm

pm.execute_notebook(
    'path/to/input.ipynb',
    'path/to/output.ipynb',
    parameters=dict(alpha=0.6, ratio=0.1)
)

Per the Papermill execute docs (opens in new window).

Step 4 - CLI execution

# Local in/out
papermill local/input.ipynb local/output.ipynb -p alpha 0.6 -p ratio 0.1

# S3 output
papermill local/input.ipynb s3://bkt/output.ipynb -p alpha 0.6 -p l1_ratio 0.1

Parameter flags per the Papermill execute docs (opens in new window):

FlagMeaning
-p NAME VALSimple parameter (auto-typed)
-r NAME VALRaw string (preserve as string)
-f file.yamlParameters from YAML file
-y "key: val"Inline YAML (supports lists, dicts)
-b base64yamlBase64-encoded YAML

Step 5 - Use as regression test

import json
import papermill as pm
import nbformat

def test_analysis_with_known_inputs(tmp_path):
    out_path = tmp_path / "out.ipynb"
    pm.execute_notebook(
        'analysis.ipynb',
        str(out_path),
        parameters=dict(seed=42, n_samples=1000),
    )

    nb = nbformat.read(str(out_path), as_version=4)
    final_cell = nb.cells[-1]
    output_text = final_cell.outputs[0]['text']
    result = json.loads(output_text)

    assert abs(result['mean'] - 0.5) < 0.01
    assert result['n'] == 1000

The output notebook is artifact-friendly - attach to CI runs for review when assertions fail.

Step 6 - Parameter sweeps in CI

# GitHub Actions matrix sweep
strategy:
  matrix:
    seed: [42, 123, 7]
    n_samples: [100, 1000]
steps:
  - run: |
      papermill analysis.ipynb out-${{ matrix.seed }}-${{ matrix.n_samples }}.ipynb \
        -p seed ${{ matrix.seed }} \
        -p n_samples ${{ matrix.n_samples }}
  - uses: actions/upload-artifact@v4
    with:
      name: papermill-output-${{ matrix.seed }}-${{ matrix.n_samples }}
      path: out-${{ matrix.seed }}-${{ matrix.n_samples }}.ipynb

Step 7 - Pair with nbval / testbook

ToolStrengthPair with papermill how
nbvalFull-notebook output regressionRun papermill first (parameter inject) → run nbval on output
testbookFunction-level unit teststestbook can use papermill's executor under the hood - see testbook configuration for execute_kwargs

Papermill is the engine; nbval and testbook are the assertion layers. Use all three for production notebook QA.

Step 8 - TQDM progress descriptions

Add comments at cell start:

#papermill_description=load_data
df = load_dataset()

#papermill_description=train_model
model.fit(df)

Per the Papermill execute docs (opens in new window): integrates with TQDM for meaningful CI progress indicators.

Worked example

An analyst ships analysis.ipynb that samples a distribution and prints a JSON summary in its last cell. To gate it in CI:

  1. Tag the top cell parameters with defaults seed = 0 and n_samples = 100.
  2. Add a pytest test that runs pm.execute_notebook('analysis.ipynb', out_path, parameters=dict(seed=42, n_samples=1000)).
  3. Read out.ipynb with nbformat, parse the last cell's output text as JSON, and assert abs(result['mean'] - 0.5) < 0.01 and result['n'] == 1000.

On a green run the assertions pass and out.ipynb uploads as a CI artifact. When a refactor shifts the sampled mean, the assertion fails, the job goes red, and the attached output notebook shows the offending cell for review.

Anti-patterns

Anti-patternWhy it failsFix
Forget the parameters cell tagParameters never inject; notebook runs with defaultsTag the cell explicitly (Step 2)
Mix -p and -r types incorrectly-p version 1.0 becomes float 1.0; loses leading zeros etc.Use -r for strings (Step 4)
Run papermill against side-effect notebooks (writes to prod DB)Papermill is non-transactional; partial failures leave bad stateUse ephemeral workdirs / staging credentials in test runs
Ignore the output notebook (only check exit code)Subtle errors visible only in cell outputsSave + inspect output notebook (Step 5); upload as artifact (Step 6)
Skip seed parameterizationTests flake on stochastic modelsAlways -p seed N for reproducible runs

Limitations

  • Papermill executes via the standard Jupyter kernel; very long notebooks have higher OOM risk than equivalent .py scripts.
  • Output notebooks are large (full re-render of all cells); CI artifact storage adds up - consider retention policy.
  • Parameter injection is one-shot at notebook start; cannot re-parameterize mid-run.

References

Related skills

deepchecks-tests

Run Deepchecks suites (data integrity, train-test validation, model evaluation) on tabular / NLP / vision data + models. Pass `result.passed_conditions()` to CI to gate on regressions; the same checks run during research, CI, and production monitoring per the Deepchecks lifecycle posture. Use before training to catch train-test leakage and data-integrity defects in a tabular, NLP, or vision dataset, and to re-run the same suite on production samples to detect drift.

evidently-monitoring

Use Evidently OSS (100+ evaluation metrics, declarative testing API) to detect data drift, target drift, and model-performance regression, wired into CI as a gate (a Report run with include_tests) and into production monitoring as a continuous check; reports as HTML + JSON for both human review and pipeline assertions. Includes a drift-alert triage playbook: classify the fired alert's signal, rank root-cause hypotheses (upstream schema change, pipeline bug, training-serving skew, seasonality, genuine population shift), and pick rollback, retrain, quarantine, or alert re-tuning. Use when you need a drift or quality gate, a scheduled monitoring job, or a structured triage of a fired drift alert, for a tabular ML model. Built on the Evidently API specifically: for DeepChecks-based validation suites use deepchecks-tests instead.

fairlearn-fairness

Compute group fairness metrics (selection rate, demographic parity, equalized odds) per sensitive feature with `MetricFrame`, then mitigate disparities using Reductions algorithms (`ExponentiatedGradient` with constraint = `DemographicParity`/`EqualizedOdds`). Wire group-disaggregated assertions into the model-evaluation gate. Use when a model's decisions affect people and a stakeholder, auditor, or regulation (ECOA, GDPR Art. 22, EU AI Act high-risk) requires evidence of per-group outcomes, or when someone reports the model treats a specific group worse.

giskard-tests

Test ML models with Giskard's scan() vulnerability detector + test catalog (performance, robustness, fairness, data leakage, ethical issues) for tabular and NLP models. Wrap a prediction function in giskard.Model + a DataFrame in giskard.Dataset; emit test suites that pass/fail in CI. Use when a trained tabular or NLP model is about to ship with no test suite of its own, or when a feature-engineering or hyperparameter change needs a pre-merge scan for newly introduced vulnerabilities.

model-performance-regression-gate

Computes held-out metrics (accuracy, F1, AUC, RMSE) for a retrained model and compares them against the current production model, failing promotion when any metric regresses beyond a configured tolerance. Adds per-segment checks via Deepchecks WeakSegmentsPerformance so a model that improves globally but regresses on a key slice is still blocked. Use when a retrained model is a candidate for promotion and the CI pipeline must enforce a per-metric pass/fail gate before the artifact is pushed to the model registry.

model-risk-evidence-matrix

Assigns an ML model to a low, medium, or high risk tier from what its predictions decide about people, then derives the fairness and explainability evidence that tier must produce: group metrics per declared sensitive feature, intersectional breakdowns with per-cell counts, vulnerability scan categories, a drift monitoring plan, and per-prediction explanation logs. Supplies conventional demographic parity difference bands, a per-vulnerability-category blocking table, evidence rules marking a bundle incomplete or self-contradicting, and a fairness gating workflow that walks a candidate's model card + evidence bundle to a promote / needs-work / block verdict with refuse rules; a reference covers producing the explanation records with Alibi Explain. Use when a model release candidate is up for promotion and someone must decide which fairness artifacts are mandatory, when a declared risk tier's evidence bundle must be checked against what the tier demands, or when the evidence review must gate the promotion.