Promote, reject, and roll back model versions#

Goal. Work through a bounded, reproducible example and inspect the evidence before connecting an external service.

Prerequisites. Base FraudTwin install. Optional extras and Docker commands are clearly marked.

Produces. Tables, fingerprints, manifests, and verification output.

Source size. The default cells generate approximately 1,000 logical payments; increase duration and population together for a 10,000-payment run.

Offline path. All marked offline cells run without Docker or network services. Service cells are optional and explicitly marked in notebook metadata.

Cleanup. Outputs are written under a temporary directory; remove any local run directory if you changed the output location.

!pip install mlflow
import os

os.environ["MLFLOW_TRACKING_URI"] = "http://127.0.0.1:5000"
print("MLflow tracking URI configured: http://127.0.0.1:5000")
Requirement already satisfied: mlflow in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (3.16.1)
Requirement already satisfied: mlflow-skinny==3.16.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.16.1)
Requirement already satisfied: mlflow-tracing==3.16.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.16.1)
Requirement already satisfied: Flask-CORS<7 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (6.0.5)
Requirement already satisfied: Flask<4 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.1.3)
Requirement already satisfied: aiohttp<4,>=3.7.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.14.3)
Requirement already satisfied: alembic!=1.10.0,<2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (1.20.0)
Requirement already satisfied: cryptography<51,>=43.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (50.0.1)
Requirement already satisfied: docker<8,>=4.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (7.2.0)
Requirement already satisfied: graphene<4 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.4.3)
Requirement already satisfied: gunicorn<27 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (26.2.0)
Requirement already satisfied: huey<4,>=2.5.4 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.4.0)
Requirement already satisfied: matplotlib<4 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (3.11.2)
Requirement already satisfied: numpy<3 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (2.5.3)
Requirement already satisfied: pandas<4 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (2.3.3)
Requirement already satisfied: pyarrow<26,>=4.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (25.0.1)
Requirement already satisfied: scikit-learn<2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (1.9.1)
Requirement already satisfied: scipy<2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (1.18.1)
Requirement already satisfied: skops<1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (0.16.0)
Requirement already satisfied: sqlalchemy<3,>=1.4.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow) (2.0.53)
Requirement already satisfied: anyio!=4.15.0,<5,>=3.6.2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (4.15.1)
Requirement already satisfied: cachetools<8,>=5.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (7.2.0)
Requirement already satisfied: click<9,>=7.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (8.5.0)
Requirement already satisfied: cloudpickle<4 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (3.1.2)
Requirement already satisfied: databricks-sdk<1,>=0.20.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (0.141.0)
Requirement already satisfied: fastapi<1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (0.141.1)
Requirement already satisfied: gitpython<4,>=3.1.9 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (3.1.62)
Requirement already satisfied: importlib_metadata!=4.7.0,<10,>=3.7.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (9.0.1)
Requirement already satisfied: opentelemetry-api<3,>=1.9.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (1.44.0)
Requirement already satisfied: opentelemetry-proto<3,>=1.9.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (1.44.0)
Requirement already satisfied: opentelemetry-sdk<3,>=1.9.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (1.44.0)
Requirement already satisfied: packaging<27 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (26.3)
Requirement already satisfied: protobuf<8,>=3.12.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (6.33.6)
Requirement already satisfied: pydantic<3,>=2.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (2.13.5)
Requirement already satisfied: python-dotenv<2,>=0.19.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (1.2.3)
Requirement already satisfied: pyyaml<7,>=5.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (6.0.3)
Requirement already satisfied: requests<3,>=2.17.3 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (2.34.2)
Requirement already satisfied: sqlparse<1,>=0.4.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (0.6.0)
Requirement already satisfied: starlette<2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (1.7.0)
Requirement already satisfied: typing-extensions<5,>=4.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (4.16.0)
Requirement already satisfied: uvicorn<1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from mlflow-skinny==3.16.1->mlflow) (0.53.0)
Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (2.7.1)
Requirement already satisfied: aiosignal>=1.4.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (1.4.0)
Requirement already satisfied: attrs>=17.3.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (26.1.0)
Requirement already satisfied: frozenlist>=1.1.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (1.8.0)
Requirement already satisfied: multidict<7.0,>=4.5 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (6.9.1)
Requirement already satisfied: propcache>=0.2.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (0.5.4)
Requirement already satisfied: yarl<2.0,>=1.17.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from aiohttp<4,>=3.7.0->mlflow) (1.25.1)
Requirement already satisfied: Mako in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from alembic!=1.10.0,<2->mlflow) (1.4.3)
Requirement already satisfied: idna>=2.8 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from anyio!=4.15.0,<5,>=3.6.2->mlflow-skinny==3.16.1->mlflow) (3.19)
Requirement already satisfied: cffi>=2.0.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from cryptography<51,>=43.0.0->mlflow) (2.1.1)
Requirement already satisfied: google-auth~=2.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from databricks-sdk<1,>=0.20.0->mlflow-skinny==3.16.1->mlflow) (2.58.0)
Requirement already satisfied: urllib3<3,>=1.26 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from databricks-sdk<1,>=0.20.0->mlflow-skinny==3.16.1->mlflow) (2.7.0)
Requirement already satisfied: typing-inspection>=0.4.2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from fastapi<1->mlflow-skinny==3.16.1->mlflow) (0.4.4)
Requirement already satisfied: annotated-doc>=0.0.2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from fastapi<1->mlflow-skinny==3.16.1->mlflow) (0.0.5)
Requirement already satisfied: blinker>=1.9.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from Flask<4->mlflow) (1.9.0)
Requirement already satisfied: itsdangerous>=2.2.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from Flask<4->mlflow) (2.2.0)
Requirement already satisfied: jinja2>=3.1.2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from Flask<4->mlflow) (3.1.6)
Requirement already satisfied: markupsafe>=2.1.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from Flask<4->mlflow) (3.0.3)
Requirement already satisfied: werkzeug>=3.1.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from Flask<4->mlflow) (3.1.8)
Requirement already satisfied: gitdb<5,>=4.0.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from gitpython<4,>=3.1.9->mlflow-skinny==3.16.1->mlflow) (4.0.12)
Requirement already satisfied: smmap<6,>=3.0.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from gitdb<5,>=4.0.1->gitpython<4,>=3.1.9->mlflow-skinny==3.16.1->mlflow) (5.0.3)
Requirement already satisfied: pyasn1-modules>=0.2.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from google-auth~=2.0->databricks-sdk<1,>=0.20.0->mlflow-skinny==3.16.1->mlflow) (0.4.2)
Requirement already satisfied: graphql-core<3.3,>=3.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from graphene<4->mlflow) (3.2.12)
Requirement already satisfied: graphql-relay<3.3,>=3.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from graphene<4->mlflow) (3.2.0)
Requirement already satisfied: python-dateutil<3,>=2.7.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from graphene<4->mlflow) (2.9.0.post0)
Requirement already satisfied: zipp>=3.20 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from importlib_metadata!=4.7.0,<10,>=3.7.0->mlflow-skinny==3.16.1->mlflow) (4.1.0)
Requirement already satisfied: contourpy>=1.0.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from matplotlib<4->mlflow) (1.4.0)
Requirement already satisfied: cycler>=0.10 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from matplotlib<4->mlflow) (0.12.1)
Requirement already satisfied: fonttools>=4.28.2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from matplotlib<4->mlflow) (4.66.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from matplotlib<4->mlflow) (1.5.1)
Requirement already satisfied: pillow>=9 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from matplotlib<4->mlflow) (12.3.0)
Requirement already satisfied: pyparsing>=3 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from matplotlib<4->mlflow) (3.3.3)
Requirement already satisfied: opentelemetry-semantic-conventions==0.65b0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from opentelemetry-sdk<3,>=1.9.0->mlflow-skinny==3.16.1->mlflow) (0.65b0)
Requirement already satisfied: pytz>=2020.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from pandas<4->mlflow) (2026.3.post1)
Requirement already satisfied: tzdata>=2022.7 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from pandas<4->mlflow) (2026.4)
Requirement already satisfied: annotated-types>=0.6.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from pydantic<3,>=2.0.0->mlflow-skinny==3.16.1->mlflow) (0.8.0)
Requirement already satisfied: pydantic-core==2.46.5 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from pydantic<3,>=2.0.0->mlflow-skinny==3.16.1->mlflow) (2.46.5)
Requirement already satisfied: six>=1.5 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from python-dateutil<3,>=2.7.0->graphene<4->mlflow) (1.17.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from requests<3,>=2.17.3->mlflow-skinny==3.16.1->mlflow) (3.5.1)
Requirement already satisfied: certifi>=2023.5.7 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from requests<3,>=2.17.3->mlflow-skinny==3.16.1->mlflow) (2026.7.22)
Requirement already satisfied: joblib>=1.4.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from scikit-learn<2->mlflow) (1.6.0)
Requirement already satisfied: narwhals>=2.0.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from scikit-learn<2->mlflow) (2.26.0)
Requirement already satisfied: threadpoolctl>=3.5.0 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from scikit-learn<2->mlflow) (3.6.0)
Requirement already satisfied: prettytable>=3.9 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from skops<1->mlflow) (3.18.0)
Requirement already satisfied: greenlet>=1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from sqlalchemy<3,>=1.4.0->mlflow) (3.5.6)
Requirement already satisfied: h11>=0.8 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from uvicorn<1->mlflow-skinny==3.16.1->mlflow) (0.16.0)
Requirement already satisfied: pycparser in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from cffi>=2.0.0->cryptography<51,>=43.0.0->mlflow) (3.0)
Requirement already satisfied: wcwidth>=0.3.5 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from prettytable>=3.9->skops<1->mlflow) (0.8.3)
Requirement already satisfied: pyasn1<0.7.0,>=0.6.1 in /home/emc/Projects/test/.venv/lib/python3.14/site-packages (from pyasn1-modules>=0.2.1->google-auth~=2.0->databricks-sdk<1,>=0.20.0->mlflow-skinny==3.16.1->mlflow) (0.6.4)
MLflow tracking URI configured: http://127.0.0.1:5000

Optional MLflow setup#

The notebook works without a server by using a temporary local tracking store. To make the run appear in the MLflow web UI, start the tracking server from a separate terminal in the repository root and keep that terminal running:

poetry install -E mlflow
poetry run mlflow server --host 127.0.0.1 --port 5000

Then open http://127.0.0.1:5000 in the same machine’s browser. localhost:5000 works too, but only while the command above is running; opening the URL alone cannot start MLflow. If the notebook runs in Docker, WSL, or a remote Jupyter server, use the forwarded/public host and port instead of your browser’s localhost.

After the server is running, execute the first optional MLflow setup cell below. It sets the tracking URI in the Jupyter kernel. Then execute the MLflow tracking cell near the end of the notebook; merely reading this Markdown code block does not configure the kernel:

import os
os.environ["MLFLOW_TRACKING_URI"] = "http://127.0.0.1:5000"

If the setup cell is skipped, the notebook intentionally uses a temporary local store and the browser UI will remain empty. This tutorial logs a standard MLflow tracking run, not a GenAI trace. In MLflow 3, choose the Model training tab at the top of the experiment page; the GenAI overview will correctly show zero traces. If the URL contains workflowType=genai, remove that query parameter or use the direct run link printed by the tracking cell. See the MLflow tracking server guide for server and client configuration details.

Set up a deterministic source run

import json
from pathlib import Path
from tempfile import TemporaryDirectory

import polars as pl

from fraudtwin.config import load_config
from fraudtwin.generation import generate

root = next(
    (p for p in (Path.cwd(), *Path.cwd().parents) if (p / "configs" / "minimal.yaml").exists()),
    Path.cwd(),
)
base = load_config(root / "configs" / "minimal.yaml")
# Scale the population so the bounded example produces about 1,000 payments.
population = base.population.model_copy(
    update={
        "customers": 200,
        "accounts": 300,
        "cards": 240,
        "devices": 240,
        "pix_keys": 160,
        "merchants": 60,
    }
)
simulation = base.simulation.model_copy(update={"duration_days": 10})
fraud = base.fraud.model_copy(update={"enabled": True, "target_rate": 0.05})
config = base.model_copy(
    update={"population": population, "simulation": simulation, "fraud": fraud}
)
data = generate(config, write=False)
run_id = data.run_id
payments = pl.DataFrame([item.model_dump(mode="json") for item in data.behavior.payments])
print("Generated source run")
print(f"  run id: {run_id}")
print(f"  payments: {len(payments):,}")
print(f"  events: {len(data.behavior.payment_events):,}")
Generated source run
  run id: RUN-2a3ad02ee370aeb8
  payments: 1,092
  events: 4,699

Score a small evaluation sample

from fraudtwin.ml import heuristic_predictions

dataset = data.require_dataset()
policy_rows = dataset.rows[:20]
scores = heuristic_predictions(policy_rows)
metrics = {"rows": len(scores), "mean_score": sum(s.fraud_score for s in scores) / len(scores)}
print("Candidate metrics")
print(f"  rows scored: {metrics['rows']:,}")
print(f"  mean fraud score: {metrics['mean_score']:.3f}")
Candidate metrics
  rows scored: 20
  mean fraud score: 0.245

Create a candidate model record

candidate = {"version": "candidate-1", "schema": "pit-v1", "metrics": metrics}
display(
    pl.DataFrame(
        [{"version": candidate["version"], "schema": candidate["schema"], **candidate["metrics"]}]
    )
)
shape: (1, 4)
versionschemarowsmean_score
strstri64f64
"candidate-1""pit-v1"200.244821

Promote the candidate

registry = {"Production": None, "Staging": candidate}
registry["Production"] = registry["Staging"]
print("Promotion")
print(f"  staging -> production: {registry['Production']['version']}")
Promotion
  staging -> production: candidate-1

Roll back to the previous version

registry["Staging"] = {**candidate, "version": "candidate-2"}
registry["Production"] = candidate
print("Rollback")
print(f"  production restored to: {registry['Production']['version']}")
Rollback
  production restored to: candidate-1

Verify the promotion contract

assert registry["Production"]["schema"] == "pit-v1"
print("Optional MLflow registry calls can replace this local state machine.")
Optional MLflow registry calls can replace this local state machine.

Verify invariants and clean up

# A compact inspection is more useful than printing an entire run.
sample_columns = [
    c for c in ("payment_id", "amount", "initiated_at", "payer_account_id") if c in payments.columns
]
sample_rows = payments.select(sample_columns).head(8).to_dicts()
print(f"Sample payments ({len(sample_rows)} of {payments.height} rows):")
for row in sample_rows:
    print(
        f"  - {row.get('payment_id')}: amount={row.get('amount')}, "
        f"initiated_at={row.get('initiated_at')}, payer={row.get('payer_account_id')}"
    )
nulls = {name: count for name, count in payments.null_count().to_dicts()[0].items() if count}
print("\nData quality summary:")
print(f"  rows: {payments.height}")
print(f"  columns: {payments.width}")
if not nulls:
    print("  nulls: none")
else:
    print("  columns with nulls:")
    for name, count in sorted(nulls.items()):
        print(f"    - {name}: {count}")
Sample payments (8 of 1092 rows):
  - PAY-00000001: amount=70.7, initiated_at=2026-01-03T16:25:00Z, payer=ACC-000123
  - PAY-00000002: amount=25.52, initiated_at=2026-01-05T11:37:00Z, payer=ACC-000174
  - PAY-00000003: amount=18.37, initiated_at=2026-01-05T11:21:00Z, payer=ACC-000174
  - PAY-00000004: amount=5.54, initiated_at=2026-01-02T22:30:00Z, payer=ACC-000003
  - PAY-00000005: amount=13.71, initiated_at=2026-01-02T09:41:00Z, payer=ACC-000029
  - PAY-00000006: amount=70.06, initiated_at=2026-01-04T10:40:00Z, payer=ACC-000179
  - PAY-00000007: amount=42.73, initiated_at=2026-01-02T18:04:00Z, payer=ACC-000247
  - PAY-00000008: amount=36.42, initiated_at=2026-01-05T09:27:00Z, payer=ACC-000255

Data quality summary:
  rows: 1092
  columns: 15
  columns with nulls:
    - card_id: 565
    - merchant_id: 565
    - payee_account_id: 86
    - payee_institution_id: 86
    - payee_pix_key_id: 857
    - payer_institution_id: 86
    - payer_pix_key_id: 857

Optional service integration

summary = {
    "run_id": run_id,
    "payments": len(data.behavior.payments),
    "payment_events": len(data.behavior.payment_events),
    "fraud_records": len(data.behavior.fraud_records),
}
assert summary["payments"] == len(payments)
assert summary["payments"] > 0
print("Generated dataset")
print(f"  run id: {summary['run_id']}")
print(f"  payments: {summary['payments']:,}")
print(f"  payment events: {summary['payment_events']:,}")
print(f"  fraud records: {summary['fraud_records']:,}")
Generated dataset
  run id: RUN-2a3ad02ee370aeb8
  payments: 1,092
  payment events: 4,699
  fraud records: 51

Review the expected outcome

import os
import warnings
from urllib.parse import urlparse

warnings.filterwarnings("ignore", message="IProgress not found.*")

try:
    import mlflow
    from IPython.display import Markdown, display
    from mlflow.tracking import MlflowClient

    tracking_uri = os.getenv("MLFLOW_TRACKING_URI")
    with TemporaryDirectory(prefix="fraudtwin-mlflow-") as mlflow_dir:
        mlflow.set_tracking_uri(tracking_uri or Path(mlflow_dir).as_uri())
        mlflow.set_experiment("fraudtwin-tutorial")
        with mlflow.start_run() as run:
            mlflow.log_params({"schema": candidate["schema"]})
            mlflow.log_metric("mean_score", metrics["mean_score"])
            artifact = Path(mlflow_dir) / "candidate.json"
            artifact.write_text(json.dumps(candidate), encoding="utf-8")
            mlflow.log_artifact(str(artifact))
            tracked_id = run.info.run_id
        client = MlflowClient()
        experiment = client.get_experiment_by_name("fraudtwin-tutorial")
        tracked = client.get_run(tracked_id)
        experiments = client.search_experiments()
        print("MLflow tracking")
        print(f"  tracking URI: {mlflow.get_tracking_uri()}")
        print(f"  experiment: {experiment.name} (id={experiment.experiment_id})")
        display(
            pl.DataFrame(
                [
                    {"experiment": item.name, "experiment_id": item.experiment_id}
                    for item in experiments
                ]
            )
        )
        print("  tracked: yes")
        print(f"  run id: {tracked.info.run_id}")
        print(f"  metrics logged: {len(tracked.data.metrics)}")
        if urlparse(tracking_uri or "").scheme in {"http", "https"}:
            run_url = (
                f"{tracking_uri.rstrip('/')}/#/experiments/"
                f"{tracked.info.experiment_id}/runs/{tracked.info.run_id}"
            )
            display(Markdown(f"[Open this run in MLflow]({run_url})"))
        else:
            print("  browser link: unavailable for the temporary local store")
except Exception as exc:
    print("MLflow tracking is unavailable; the offline promotion state remains valid.")
    print("  tracked: no")
    print("  offline_fallback: yes")
    print(f"  reason: {type(exc).__name__}")
🏃 View run angry-shoat-568 at: http://127.0.0.1:5000/#/experiments/1/runs/bdb270b811f3440d8afaa0ae041ddd01
🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1
MLflow tracking
  tracking URI: http://127.0.0.1:5000
  experiment: fraudtwin-tutorial (id=1)
shape: (2, 2)
experimentexperiment_id
strstr
"fraudtwin-tutorial""1"
"Default""0"
  tracked: yes
  run id: bdb270b811f3440d8afaa0ae041ddd01
  metrics logged: 1

Open this run in MLflow

Record the generated shape and tutorial contract.#

summary = {
    "payments": len(data.behavior.payments),
    "events": len(data.behavior.payment_events),
}
print("Tutorial contract")
print(f"  payments: {summary['payments']:,}")
print(f"  events: {summary['events']:,}")
assert summary["payments"] >= 0
Tutorial contract
  payments: 1,092
  events: 4,699
assert registry["Production"]["schema"] == "pit-v1"
print("Offline promotion fallback")
print("  offline_fallback: yes")
print(f"  production version: {registry['Production']['version']}")
Offline promotion fallback
  offline_fallback: yes
  production version: candidate-1