Orchestrate

Report an issue

Something wrong, out of date or unclear? Tell us. Corrections are prioritized over new topics. You don't need to sign in.

0 / 2000

Don't include personal or confidential company details. Reports are reviewed with AI assistance.

Process mining to find AI opportunities

Read an event log for variants, rework, waiting time and touchless rate, find where a process really stalls, and turn the top delay into a measurable problem.

Updated Nov 1, 2026Foundational 9 minDeep 40 min
Foundational layer · 9 min read

The 60-second version

Every time someone creates, changes or releases a document in SAP, the system writes down what happened and when. Put those records in order for each business case, and you get an event log: order 1002001 was created at 03:29, its delivery was created at 04:05, it shipped the next morning and was invoiced at 22:00.

Process mining reads thousands of those logs at once. It shows the process as it really runs, not as the slide says it runs. It answers four questions with numbers:

  • Variants: how many different paths do cases take, and which are common?
  • Rework: which steps happen twice or more in the same case?
  • Waiting time: where do cases sit idle between steps, and for how long?
  • Touchless rate: what share of cases go through with no one stepping in?

For an FDE, that turns discovery from opinion into evidence. Interviews tell you what people think hurts. The event log tells you where the hours actually go. It also hands you a baseline from the system, before anyone proposes a solution.

Why it matters to the business

Most AI requests point at the step people complain about. That step is often not where the time goes.

Take a made-up quarter at Calder Pumpworks, the fictional pump maker this course uses. The head of order management asks: "Can AI clear our credit blocks faster?" The event log of 600 sales orders gives a sharper picture:

  • 63% of orders flow straight through: order, delivery, goods issue, invoice.
  • About one order in four stops on a credit block. Those orders take nearly a day longer.
  • The wait from block to release is about 24 hours at the median. Blocks set on a Friday wait about 63 hours.
  • Most blocked orders come from distributors, and almost all are released in the end.

That changes the conversation. The pattern is a queue: blocks wait for a once-a-day review, and weekends add two days. A second review slot might fix most of it with no AI at all. If AI helps, the evidence says where: preparing each decision so the queue moves faster, not deciding for the analyst.

Three business wins come from mining first:

  • Less waste. You don't fund an AI project for a problem a schedule change fixes.
  • A real baseline. "24 hours at the median, from the system" is a number a sponsor can sign. "It feels slow" is not.
  • A way to measure later. The same log, rerun after go-live, shows whether the change worked.

The risk is reading too much into the log. It shows elapsed time, not effort, and it shows what happened, not why. Pair it with interviews and shadowing, as the discovery topic teaches.

How SAP does it

SAP's process mining product is SAP Signavio Process Intelligence. As of 1 November 2026, its product page describes:

  • Connectors for SAP and non-SAP systems, with ready-made process flows and metrics.
  • Process Atoms: reusable business rules used to check whether cases conform.
  • Simulation: testing a process change against the event data before going live.
  • AI-assisted root cause analysis and generative AI answers to process questions.
  • Actions: alerts, workflows or bots, including through SAP Build Process Automation.
  • A free trial and a request form for a free analysis.

SAP Signavio Process Insights is the faster entry point. SAP Learning says it extracts data from SAP ERP and comes with more than 200 process performance indicators, benchmarks and correction recommendations (SAP Learning). SAP's May 2026 release post says the two products are being brought together into one experience (SAP Signavio). Ask your SAP contact which names and editions your contract covers.

SAP is also adding agents on top. SAP News on 26 October 2026 describes the Process Consulting Agent, which answers process questions in natural language, as part of the Process Transformation Assistant, in a phased general availability. It also names SAP Signavio agent mining for finding where agents could help (SAP News).

Without Signavio, open-source tools such as pm4py do the core analysis on an exported log. That is what the deep layer builds.

From event log to AI decision

What the log shows What it usually means Typical next step Is AI the answer?
Long wait before a manual step, worse on Fridays A queue: people review in batches Change the review schedule; prepare cases before review Maybe later, to prepare each case
The same step done twice Rework: wrong first time Fix the master data or the rule behind it Rarely; fix the cause first
Many small variants Exceptions handled ad hoc Standardize the common ones Sometimes, to sort or route cases
Low touchless rate on simple cases Manual checks on safe cases Automate with rules in standard SAP Rules first, AI for the judgment cases
Long wait after a system step A batch job's schedule Check the job timing No

The pattern is the same as the opportunity assessment: remove the cause, use standard SAP, then consider AI for what is left.

Questions to ask

Your team:

  • Which process and which case do we mine: a sales order, an order item, a delivery?
  • Over what period, and how many cases? Does it include a quarter-end?
  • Which waits are by design (payment terms, a nightly billing run) and which are friction?
  • What is the touchless rate today, and how do we define "touched"?

Your vendor or partner:

  • Which source tables or APIs feed the event log, and how often?
  • How is personal data handled in extraction? Is it pseudonymized?
  • Which findings will you confirm by interviews or shadowing before proposing a solution?
  • What baseline will you hand over, with what definition, so we can rerun it after go-live?

Your SAP account team:

  • Which SAP Signavio products and editions do we already license?
  • Are the AI features we saw in a demo generally available for our edition and region?

Common misconceptions

  • "Process mining shows how long people work on a case." It shows elapsed time between recorded steps. Effort comes from shadowing.
  • "The happy path is the problem to fix." The happy path is usually fine. The time goes into detours: blocks, changes, rework.
  • "More variants means a worse process." A few variants carry most cases. Count how many cases each covers before worrying.
  • "Process mining finds AI use cases by itself." It finds where time goes. Deciding whether AI, a rule, or a schedule change fits is a separate step.
  • "The event log is already in SAP, ready to use." The raw records are there. Building a clean log means choosing the case, joining tables and handling changes. That is most of the effort.

Key terms

All course terms, A to Z, in the glossary

  • Event log: records of what happened, each with a case ID, an activity name and a timestamp.
  • Case: one instance of the process, such as one sales order.
  • Variant: a set of cases that follow the same sequence of steps.
  • Rework: a step that happens more than once in the same case.
  • Waiting time: the time between one step and the next in a case.
  • Touchless rate: the share of cases with no manual step after creation.
  • Detour: a step only some cases take, such as a credit block.
  • Conformance checking: comparing what happened with what should have happened.
  • Object-centric process mining: analysis where one event can belong to several objects, such as an order and a delivery.

Check yourself

Pick one answer for each question. The explanation appears after you choose.

1What does process mining give an FDE that interviews alone don't?

Answer: D. The event log shows the process as it ran, with waits, rework and variants counted from system data. It shows elapsed time, not people's effort, so it complements interviews and shadowing rather than replacing them.

2Credit-blocked orders at Calder wait about a day, and much longer when blocked on a Friday. What does that pattern most suggest?

Answer: C. A wait that jumps for Friday blocks points at when the work is picked up, not how long it takes. A schedule change, such as a second review slot, may fix most of it before any AI is considered.

3A partner proposes AI after mining your process. Which question tests their finding best?

Answer: A. Some waits are intended, like payment terms or a nightly billing run, and the log can't say why a case waited. A finding confirmed by interviews or shadowing is one you can act on.

4What do SAP Signavio Process Insights and Process Intelligence each offer, as SAP describes them?

Answer: A. SAP Learning describes Process Insights as a fast track with more than 200 performance indicators and recommendations, and Process Intelligence as analysis of event logs across systems. SAP's May 2026 release post says the two are being brought into one experience.

5The mining shows the same price change happening twice in many spare-part orders. What is the usual first move?

Answer: B. Rework usually has a fixable cause in master data or a rule. Removing the cause comes before any automation, and an agent changing SAP prices without approval breaks the course's approval rule.

6Why is "a baseline from the event log" valuable when you frame the problem?

Answer: B. A baseline counted from system data is defensible and repeatable. Rerunning the same query on the next quarter's log shows whether the change worked.
Deep layer · 40 min read

Mental model

A process is a set of cases; every case leaves a trail of timestamps.

Process mining is three moves on that trail:

  1. Choose the case. Decide what one instance of the process is: a sales order, an order item, a delivery. Everything else depends on it.
  2. Order the events. Sort each case's events by time. Now each case is a sequence, and each pair of neighbors is a wait.
  3. Count and compare. Group identical sequences into variants. Sum the waits per step pair. Compare cases that take a detour with cases that don't.

The FDE's question sits on top: where does the time go that someone could take back? Not every hour is waste. A nightly billing run makes every order wait until 22:00, by design. A credit block that waits for Monday's review is a queue someone chose not to staff. Mining finds both. Your job is to tell them apart, then measure the one worth fixing.

How it works

flowchart LR
  S[SAP tables<br/>change documents,<br/>document flow] --> E[Event log<br/>case, activity, time]
  E --> V[Variants]
  E --> R[Rework]
  E --> W[Waiting times]
  E --> T[Touchless rate]
  V --> D[Detours: extra time<br/>per optional step]
  W --> D
  D --> P[Top delay pattern<br/>drill down by segment]
  P --> F[Problem frame<br/>with a system baseline]

The event log

An event log needs three columns: a case ID, an activity name and a timestamp. SAP Learning's Signavio course lists the same three and says choosing the case ID is one of the most important decisions in data transformation (SAP Learning). Extra columns are attributes: of the case (customer, sales channel) or of the event (who did it).

The standard exchange format for such logs is XES, IEEE 1849, revised in 2023 (IEEE 1849). Most tools also read plain CSV.

Where the events come from in SAP

SAP doesn't keep a ready-made event log. You build one from records that exist for other reasons:

  • Creation data on the document itself: when the sales order, delivery or invoice was created.
  • Change documents: SAP records many field changes in the change header and item tables CDHDR and CDPOS. A price change, a block set or removed, or a date change often shows up there. Berti and colleagues use these two tables as the main change tables in their extraction approach for SAP ERP (arXiv 2110.03467).
  • Document flow: the table VBFA links an order to its deliveries and invoices, as a previous-document and next-document pair (same paper).

Which fields write change documents, and with which values, depends on the system's configuration. Treat any mapping from a table entry to an activity name as something to verify per system.

Choosing the case: convergence and divergence

One sales order can have three deliveries. One invoice can bill two orders. Force everything onto one case ID and two things go wrong, as the same paper describes:

  • Convergence: one event is copied into several cases. An invoice that bills two orders counts twice.
  • Divergence: unrelated events land in one case and look like a loop. Three deliveries of one order look like delivery rework.

Object-centric process mining avoids this by letting an event relate to several objects. The OCEL 2.0 standard records events, objects, their relations and object changes, in SQLite, XML or JSON (OCEL 2.0). For a first FDE analysis, a single case notion is usually fine. Pick the object closest to the problem (the sales order, for credit blocks) and say what you lost.

The four measures

  • Variants. Group cases by their exact sequence of activities. SAP Learning defines a variant as a set of cases with the same process flow (SAP Learning). Report the top few with their share of cases and median duration.
  • Rework. A step that happens twice or more in a case. pm4py computes rework per case as the number of activities minus the number of distinct activities (pm4py source).
  • Waiting time. For each pair of steps that directly follow each other, the time between them. Sum per pair to see where hours pile up; take the median to see the typical wait.
  • Touchless rate. The share of cases with no manual intervention after creation. There is no universal definition: write down your list of intervention steps and keep it with the number.

From waits to a delay pattern

Ranking step pairs by total hours finds the busiest edges, which are often the happy path's normal work: picking, packing, the nightly billing run. That is real time, but rarely the problem.

A detour comparison is more useful. For each step only some cases take, compare the median duration of cases with it and without it. Multiply the difference by the number of cases. That is the extra time the detour costs. The detour with the most extra hours is the top delay pattern. Then find the wait into or out of it with the most hours, and drill down: split that wait by weekday, channel or item group to see where it concentrates.

The output is a hypothesis with a system baseline: "credit blocks wait 24 hours at the median; Friday blocks wait 63". Interviews and shadowing then confirm the cause and measure the effort.

Build it yourself: mine an order-to-cash log

You will run real process mining on a made-up but SAP-shaped event log for Calder Pumpworks (fictional). The script finds the variants, rework, waiting times and touchless rate, finds the top delay pattern, and drafts a problem frame that the course's problem framer can check.

Before you start: complete Set up your computer for this course and Set up for Unit 14. They create your orchestrate-course folder, its .venv and the unit14 folder. The last step uses problem_framer.py from Problem framing; if you skipped that topic, steps 1 to 6 still work.

flowchart LR
  G[sample] --> L[(calder_o2c_events.csv)]
  L --> A[analyze]
  A --> M[process_map.md]
  A --> P[top delay pattern]
  P --> F[frame]
  F --> T[credit_block_wait.txt]
  T --> X[problem_framer.py lint]

What you need

  • The course folder and .venv from the setup topics.
  • Python 3.11 or newer. pm4py 2.7.23.11 requires it. Check with python --version.
  • About 45 minutes. Free: no account, no key, no network after the install.

Step 1: Open your course folder and check Python

  1. Open VS Code, then File > Open Folder and choose orchestrate-course in your home folder.

  2. Open a terminal and activate the virtual environment.

    Windows (PowerShell):

    .\.venv\Scripts\Activate.ps1

    macOS/Linux:

    source .venv/bin/activate
  3. Check the version:

    python --version

    You should see Python 3.11 or higher, for example Python 3.14.4. If it says 3.10, install a newer Python as in the computer setup topic, then recreate .venv.

Step 2: Install pm4py

  1. Open requirements.txt in the course folder. Add these two lines at the end (skip a line that is already there) and save:

    pm4py==2.7.23.11
    pandas==3.0.6
  2. Install:

    pip install -r requirements.txt

    It downloads pm4py and libraries it needs (numpy, scipy, matplotlib and others). This can take a few minutes.

  3. Check it:

    python -c "import pm4py, pandas; print('pm4py OK', pandas.__version__)"

    pm4py first prints a welcome box with its license notice. Then:

    pm4py OK 3.0.6

Step 3: Create the script

  1. In VS Code, right-click the unit14 folder, choose New File, name it mine_o2c.py, paste the code below and save.
"""Process mining on an SAP-shaped order-to-cash event log.

Commands:
  sample    write a made-up event log for Calder Pumpworks (fictional), seed 42
  analyze   variants, touchless rate, rework, waiting times, detours, drill-down
  frame     turn the top detour into a problem frame for problem_framer.py

The analysis uses pm4py (open source, AGPL license). No account, no network.
"""
import argparse
import datetime as dt
import random
import sys
from pathlib import Path

import pandas as pd

HERE = Path(__file__).resolve().parent
MINING = HERE / "mining"
FRAMING = HERE / "framing"
DEFAULT_LOG = MINING / "calder_o2c_events.csv"

# Steps where a person steps in after the order exists. A case with none is touchless.
INTERVENTIONS = ["Set Credit Block", "Release Credit Block", "Change Price",
                 "Change Requested Delivery Date", "Reject Sales Order"]

# ---------------------------------------------------------------------------
# 1. The sample log: 600 orders in CP10, July to September 2026 (made up)
# ---------------------------------------------------------------------------
DIRECT = ["10000001"] * 5 + [str(n) for n in range(10000021, 10000029)]
DISTRIBUTORS = ["10000002"] * 6 + [str(n) for n in range(10000031, 10000037)]


def work_time(t, start=8, end=17):
    """Move t forward to the next moment inside working hours, Monday to Friday."""
    while True:
        if t.weekday() >= 5:
            t = dt.datetime.combine(t.date() + dt.timedelta(days=1), dt.time(start))
        elif t.hour < start:
            t = t.replace(hour=start, minute=0, second=0)
        elif t.hour >= end:
            t = dt.datetime.combine(t.date() + dt.timedelta(days=1), dt.time(start))
        else:
            return t


def next_review(t, rng):
    """The credit analyst works the blocked-order list once a day, 09:00 to 11:00."""
    day = t.date()
    if t.weekday() < 5 and t.hour < 9:
        pass
    else:
        day += dt.timedelta(days=1)
    while day.weekday() >= 5:
        day += dt.timedelta(days=1)
    return dt.datetime.combine(day, dt.time(9)) + dt.timedelta(minutes=rng.randint(0, 120))


def make_case(order, rng, start):
    channel = "20" if rng.random() < 0.55 else "10"
    customer = rng.choice(DISTRIBUTORS if channel == "20" else DIRECT)
    group = "spare parts" if rng.random() < (0.7 if channel == "20" else 0.35) else "pumps"
    value = round(rng.uniform(200, 6000) if group == "spare parts" else rng.uniform(3950, 40000), 2)
    events = []

    def add(activity, when, user):
        events.append((order, activity, when, user, customer, channel, group, value))

    # Distributors send orders by EDI at any hour; direct orders are keyed in working hours.
    created = start + dt.timedelta(minutes=rng.randint(0, 24 * 60 - 1))
    if channel == "10":
        created = work_time(created) + dt.timedelta(minutes=rng.randint(0, 90))
    add("Create Sales Order", created, "edi" if channel == "20" else "dialog")
    t = created

    blocked = rng.random() < ({"10000002": 0.55}.get(customer, 0.35 if channel == "20" else 0.06))
    price_missing = group == "spare parts" and rng.random() < (0.30 if channel == "20" else 0.10)
    reblock = blocked and price_missing and rng.random() < 0.25

    if blocked:
        t += dt.timedelta(minutes=1)
        add("Set Credit Block", t, "system")
    if price_missing and not reblock:
        t = work_time(t + dt.timedelta(minutes=rng.randint(30, 300)))
        add("Change Price", t, "dialog")
        if rng.random() < 0.25:  # wrong the first time
            t = work_time(t + dt.timedelta(minutes=rng.randint(60, 600)))
            add("Change Price", t, "dialog")
    if blocked:
        t = next_review(t, rng)
        if rng.random() < 0.2:  # the analyst asks sales for context; decided next review
            t = next_review(t, rng)
        if rng.random() < 0.04:
            add("Reject Sales Order", t, "dialog")
            return events
        add("Release Credit Block", t, "dialog")
    if reblock:  # price fixed after release: higher value, blocked again
        t = work_time(t + dt.timedelta(minutes=rng.randint(30, 240)))
        add("Change Price", t, "dialog")
        t += dt.timedelta(minutes=1)
        add("Set Credit Block", t, "system")
        t = next_review(t, rng)
        add("Release Credit Block", t, "dialog")
    if rng.random() < 0.05:  # the customer asks for a later date
        t = work_time(t + dt.timedelta(minutes=rng.randint(60, 900)))
        add("Change Requested Delivery Date", t, "dialog")
        t += dt.timedelta(days=rng.randint(1, 3))
    if group == "pumps" and rng.random() < 0.08:  # not in stock: wait for production
        t += dt.timedelta(days=rng.randint(2, 5))
    # The delivery due list runs every hour; the warehouse ships in working hours.
    t = t.replace(minute=0, second=0) + dt.timedelta(hours=1, minutes=5)
    add("Create Delivery", t, "system")
    t = work_time(t + dt.timedelta(minutes=rng.randint(60, 240)), 7, 16)
    add("Post Goods Issue", t, "dialog")
    t = dt.datetime.combine(t.date(), dt.time(22))  # nightly billing run
    add("Create Invoice", t, "system")
    return events


def cmd_sample(args):
    out = Path(args.log)
    if out.exists() and not args.force:
        sys.exit(f"Sample log already exists at {show(out)}. Add --force to overwrite it.")
    rng = random.Random(42)
    first = dt.datetime(2026, 7, 1)
    rows = []
    for i in range(600):
        start = first + dt.timedelta(days=rng.randint(0, 91))
        rows += make_case(str(1002001 + i), rng, start)
    df = pd.DataFrame(rows, columns=["order", "activity", "timestamp", "user_type",
                                     "customer", "channel", "item_group", "net_value"])
    df = df.sort_values(["order", "timestamp"], kind="stable")
    df["timestamp"] = df["timestamp"].dt.strftime("%Y-%m-%d %H:%M:%S")
    out.parent.mkdir(parents=True, exist_ok=True)
    df.to_csv(out, index=False)
    print(f"Wrote {len(df)} events for {df['order'].nunique()} orders to {show(out)}")
    print("The data is made up (Calder Pumpworks is fictional). Next: python unit14/mine_o2c.py analyze")

# ---------------------------------------------------------------------------
# 2. Loading a log
# ---------------------------------------------------------------------------

def show(path):
    try:
        return str(Path(path).resolve().relative_to(Path.cwd().resolve()))
    except ValueError:
        return str(path)


def load(args):
    path = Path(args.log)
    if not path.exists():
        sys.exit(f"No event log at {args.log}. Run 'sample' first, or check the path.")
    df = pd.read_csv(path, dtype=str)
    for col in (args.case, args.activity, args.time):
        if col not in df.columns:
            sys.exit(f"Column '{col}' not in {show(path)}. Columns: {', '.join(df.columns)}")
    df[args.time] = pd.to_datetime(df[args.time])
    import pm4py  # imported here so 'sample' works even before pm4py is installed
    log = pm4py.format_dataframe(df, case_id=args.case, activity_key=args.activity,
                                 timestamp_key=args.time)
    return pm4py, log


def hours(seconds):
    return seconds / 3600


def median_hours(series):
    return float(series.median()) / 3600 if len(series) else 0.0


def case_durations(log):
    g = log.groupby("case:concept:name")["time:timestamp"]
    return (g.max() - g.min()).dt.total_seconds()


def pairs(log):
    """Each event with the next event of the same case: one row per directly-follows pair."""
    log = log.sort_values(["case:concept:name", "time:timestamp"], kind="stable").copy()
    log["next_activity"] = log.groupby("case:concept:name")["concept:name"].shift(-1)
    log["next_time"] = log.groupby("case:concept:name")["time:timestamp"].shift(-1)
    p = log.dropna(subset=["next_activity"]).copy()
    p["wait_s"] = (p["next_time"] - p["time:timestamp"]).dt.total_seconds()
    return p

# ---------------------------------------------------------------------------
# 3. The analysis
# ---------------------------------------------------------------------------

def detours(log, durations, max_share=0.5):
    """For each step that only some cases have: how much longer those cases take."""
    acts = log.groupby("concept:name")["case:concept:name"].unique()
    n_cases = len(durations)
    rows = []
    for act, cases in acts.items():
        if len(cases) > max_share * n_cases:  # main path, not a detour
            continue
        mask = durations.index.isin(cases)
        with_, without = durations[mask], durations[~mask]
        excess = (median_hours(with_) - median_hours(without)) * len(with_)
        rows.append((act, len(with_), median_hours(with_), median_hours(without), excess))
    return sorted(rows, key=lambda r: -r[4])


def top_detour(log):
    """The detour costing the most hours, and the wait into or out of it with the most hours."""
    durations = case_durations(log)
    rows = [r for r in detours(log, durations) if r[0] != "Reject Sales Order" and r[4] > 0]
    if not rows:
        sys.exit("No detour adds time in this log.")
    act = rows[0][0]
    p = pairs(log)
    touching = p[(p["concept:name"] == act) | (p["next_activity"] == act)]
    totals = touching.groupby(["concept:name", "next_activity"])["wait_s"].sum()
    a, b = totals.idxmax()
    return a, b, touching[(touching["concept:name"] == a) & (touching["next_activity"] == b)]


def chosen_pattern(log, args):
    """The wait to drill into: --pattern "A > B" if given, otherwise the top delay pattern."""
    if not args.pattern:
        return top_detour(log)
    a, sep, b = args.pattern.partition(">")
    a, b = a.strip(), b.strip()
    p = pairs(log)
    waits = p[(p["concept:name"] == a) & (p["next_activity"] == b)]
    if not sep or waits.empty:
        sys.exit(f"No case goes directly from '{a}' to '{b}'. Write it as \"Step A > Step B\", "
                 "using the names from the waiting-time list.")
    return a, b, waits


def cmd_analyze(args):
    pm4py, log = load(args)
    n_cases = log["case:concept:name"].nunique()
    durations = case_durations(log)
    print(f"Cases {n_cases}, events {len(log)}, activities {log['concept:name'].nunique()}, "
          f"{log['time:timestamp'].min():%Y-%m-%d} to {log['time:timestamp'].max():%Y-%m-%d}")
    print(f"Median throughput time {median_hours(durations):.1f} h "
          f"(first event to last event of a case)")

    print("\nTop variants (one variant = one sequence of steps)")
    variants = pm4py.get_variants(log)
    by_case = log.groupby("case:concept:name")["concept:name"].agg(tuple)
    for i, (variant, count) in enumerate(sorted(variants.items(), key=lambda kv: -kv[1])[:args.top], 1):
        med = median_hours(durations[by_case[by_case == variant].index])
        print(f"  {i}. {count:4d} cases {count / n_cases:6.1%}  median {med:6.1f} h  "
              + " > ".join(variant))
    print(f"  {len(variants)} variants in total")

    touched = set(log.loc[log["concept:name"].isin(INTERVENTIONS), "case:concept:name"])
    print(f"\nTouchless rate {1 - len(touched) / n_cases:.1%} "
          f"({n_cases - len(touched)} of {n_cases} cases have none of: {', '.join(INTERVENTIONS)})")

    print("\nRework (cases where a step happens more than once)")
    rework = pm4py.get_rework_cases_per_activity(log)
    for act, count in sorted(rework.items(), key=lambda kv: -kv[1]):
        print(f"  {act:32s} {count:4d} cases")
    if not rework:
        print("  none")

    print("\nWaiting time between steps (directly follows), by total hours")
    p = pairs(log)
    edges = p.groupby(["concept:name", "next_activity"])["wait_s"].agg(["count", "median", "sum"])
    edges = edges.sort_values("sum", ascending=False).head(args.top)
    for (a, b), row in edges.iterrows():
        print(f"  {a + ' > ' + b:58s} {int(row['count']):4d}x  median {hours(row['median']):6.1f} h"
              f"  total {hours(row['sum']):7.0f} h")

    print("\nDetours: steps only some cases have, and the extra time those cases take")
    print(f"  {'step':32s} {'cases':>5s} {'median with':>12s} {'without':>8s} {'extra hours':>12s}")
    for act, n, w, wo, excess in detours(log, durations):
        print(f"  {act:32s} {n:5d} {w:10.1f} h {wo:6.1f} h {excess:12.0f}")

    act, nxt, waits = chosen_pattern(log, args)
    total = waits["wait_s"].sum()
    label = "Chosen pattern" if args.pattern else "Top delay pattern"
    print(f"\n{label}: {act} > {nxt}, {len(waits)} times, "
          f"median {median_hours(waits['wait_s']):.1f} h, total {hours(total):.0f} h")
    days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    waits = waits.assign(weekday=pd.Categorical(waits["time:timestamp"].dt.day_name().str[:3],
                                                categories=days, ordered=True))
    # Segment by case attributes: columns with one value per case and at most 12 values.
    skip = {args.case, args.activity, args.time, "case:concept:name", "concept:name", "time:timestamp"}
    per_case = log.groupby("case:concept:name").nunique()
    segment_cols = ["weekday"] + [c for c in per_case.columns if c not in skip
                                  and not c.startswith("@@") and per_case[c].max() == 1
                                  and 1 < log[c].nunique() <= 12]
    for col in segment_cols:
        print(f"  by {col}:")
        for value, grp in waits.groupby(col, observed=True):
            print(f"    {str(value):12s} {len(grp):4d}x  median {median_hours(grp['wait_s']):6.1f} h"
                  f"  share of hours {grp['wait_s'].sum() / total:6.1%}")

    write_map(p, n_cases, args)


def write_map(p, n_cases, args):
    """A process map as a Mermaid flowchart: edges used by at least --min-share of cases."""
    edges = p.groupby(["concept:name", "next_activity"])["wait_s"].agg(["count", "median"])
    keep = edges[edges["count"] >= args.min_share * n_cases]
    names = sorted(set(keep.index.get_level_values(0)) | set(keep.index.get_level_values(1)))
    ids = {name: f"A{i}" for i, name in enumerate(names)}
    fence = "`" * 3  # a Markdown code fence
    lines = ["# Process map", "", f"Edges used by at least {args.min_share:.0%} of "
             f"{n_cases} cases. Label: times, median hours.", "", fence + "mermaid", "flowchart LR"]
    lines += [f"  {ids[n]}[{n}]" for n in names]
    for (a, b), row in keep.iterrows():
        lines.append(f"  {ids[a]} -->|{int(row['count'])}x, {hours(row['median']):.1f} h| {ids[b]}")
    lines += [fence, ""]
    MINING.mkdir(parents=True, exist_ok=True)
    out = MINING / "process_map.md"
    out.write_text("\n".join(lines), encoding="utf-8")
    print(f"\nWrote the process map to {show(out)} ({len(keep)} edges)")

# ---------------------------------------------------------------------------
# 4. From the top delay pattern to a problem frame
# ---------------------------------------------------------------------------

def cmd_frame(args):
    _, log = load(args)
    act, nxt, waits = chosen_pattern(log, args)
    where = "a delay found by process mining" if args.pattern else "the largest delay in the process"
    base = round(median_hours(waits["wait_s"]), 1)
    target = round(base * (1 - args.cut), 1)
    first, last = log["time:timestamp"].min(), log["time:timestamp"].max()
    late_week = waits[waits["time:timestamp"].dt.weekday >= 4]  # Friday to Sunday
    text = f"""# Drafted by mine_o2c.py from the event log. Review every line with the owner.
ASK: {args.ask}
PROBLEM: Orders wait {base:g} hours (median) between '{act}' and '{nxt}', {where}
ACTOR: {args.actor}
PROCESS: Order-to-cash, from '{act}' to '{nxt}'
UNIT: One case that goes from '{act}' to '{nxt}'
METRIC: Median hours from '{act}' to '{nxt}'
DIRECTION: down
UNITS: hours
BASELINE: {base:g} | system, event log {first:%Y-%m-%d} to {last:%Y-%m-%d}, {len(waits)} cases
TARGET: {target:g}
BY: {args.by}
ADDRESSABLE: {args.addressable:g} | estimate, share of the wait that is queue time, not decision work; time it while shadowing
GUARDRAIL: Released orders overdue after 30 days rise no more than 1 point
CONSTRAINT: Read-only access to SAP under the {args.actor.lower()}'s own authorizations
CONSTRAINT: The {args.actor.lower()} makes every release or rejection decision
OUT: Automatic release of blocked orders
OUT: Changing credit limits or credit check rules
OUTPUT: A ranked list of waiting cases each morning, oldest first, with the facts for each decision
ACTION: The {args.actor.lower()} works the list from the top in the standard app
CHANGES_SAP: no
BENCHMARK: A Friday afternoon and Monday 08:00 review slot ({len(late_week)} of {len(waits)} waits start Friday to Sunday)
OWNER: {args.owner}
STOP: If the median wait falls less than {max(1, round(base * args.cut / 2)):g} hours after four weeks, rethink
"""
    FRAMING.mkdir(parents=True, exist_ok=True)
    out = FRAMING / args.out
    if out.exists() and not args.force:
        sys.exit(f"{show(out)} already exists. Add --force to overwrite it.")
    out.write_text(text, encoding="utf-8")
    print(f"Pattern: {act} > {nxt}, median {base:g} h over {len(waits)} cases")
    print(f"Wrote the draft frame to {show(out)}")
    print(f"Next: python unit14/problem_framer.py lint {show(out)}")

# ---------------------------------------------------------------------------
# 5. Command line
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    sub = parser.add_subparsers(dest="command", required=True)

    def common(p):
        p.add_argument("--log", default=str(DEFAULT_LOG), help="event log CSV")
        p.add_argument("--case", default="order", help="case ID column")
        p.add_argument("--activity", default="activity", help="activity column")
        p.add_argument("--time", default="timestamp", help="timestamp column")
        p.add_argument("--pattern", default="", help='a wait to drill into, "Step A > Step B"')

    s = sub.add_parser("sample", help="write the made-up Calder event log")
    s.add_argument("--log", default=str(DEFAULT_LOG))
    s.add_argument("--force", action="store_true")
    s.set_defaults(func=cmd_sample)

    a = sub.add_parser("analyze", help="mine the log")
    common(a)
    a.add_argument("--top", type=int, default=6, help="rows to show in each list")
    a.add_argument("--min-share", type=float, default=0.05, help="smallest edge share on the map")
    a.set_defaults(func=cmd_analyze)

    f = sub.add_parser("frame", help="draft a problem frame from the top delay pattern")
    common(f)
    f.add_argument("--ask", default="Can AI clear our credit blocks faster?")
    f.add_argument("--actor", default="Credit analyst")
    f.add_argument("--owner", default="Head of Credit Management")
    f.add_argument("--cut", type=float, default=0.5, help="share of the median wait to remove")
    f.add_argument("--addressable", type=float, default=0.7)
    f.add_argument("--by", default="2027-03-31")
    f.add_argument("--out", default="credit_block_wait.txt")
    f.add_argument("--force", action="store_true")
    f.set_defaults(func=cmd_frame)

    args = parser.parse_args()
    args.func(args)
    return 0


if __name__ == "__main__":
    sys.exit(main())

Step 4: Write the sample event log

  1. Run:

    python unit14/mine_o2c.py sample
  2. You should see:

    Wrote 2830 events for 600 orders to unit14/mining/calder_o2c_events.csv
    The data is made up (Calder Pumpworks is fictional). Next: python unit14/mine_o2c.py analyze
  3. Open unit14/mining/calder_o2c_events.csv. The first lines are:

    order,activity,timestamp,user_type,customer,channel,item_group,net_value
    1002001,Create Sales Order,2026-09-20 03:29:00,edi,10000036,20,spare parts,1494.62
    1002001,Create Delivery,2026-09-20 04:05:00,system,10000036,20,spare parts,1494.62
    1002001,Post Goods Issue,2026-09-21 07:00:00,dialog,10000036,20,spare parts,1494.62
    1002001,Create Invoice,2026-09-21 22:00:00,system,10000036,20,spare parts,1494.62
    Column What it holds
    order The case ID: one sales order in company code CP10
    activity, timestamp What happened and when (local time, no time zone)
    user_type edi, dialog (a person) or system (a job)
    customer, channel Customer number and sales channel: 10 direct, 20 distributors
    item_group, net_value pumps or spare parts, and the order value in USD

If you see Sample log already exists, you ran this before. Add --force to write it again.

Step 5: Mine the log

  1. Run:

    python unit14/mine_o2c.py analyze
  2. First, pm4py prints a welcome box with its license notice. That is expected. Then you should see:

    Cases 600, events 2830, activities 9, 2026-07-01 to 2026-10-05
    Median throughput time 26.4 h (first event to last event of a case)
    
    Top variants (one variant = one sequence of steps)
      1.  380 cases  63.3%  median   13.9 h  Create Sales Order > Create Delivery > Post Goods Issue > Create Invoice
      2.  121 cases  20.2%  median   37.0 h  Create Sales Order > Set Credit Block > Release Credit Block > Create Delivery > Post Goods Issue > Create Invoice
      3.   36 cases   6.0%  median   25.3 h  Create Sales Order > Change Price > Create Delivery > Post Goods Issue > Create Invoice
      4.   17 cases   2.8%  median   37.2 h  Create Sales Order > Set Credit Block > Change Price > Release Credit Block > Create Delivery > Post Goods Issue > Create Invoice
      5.   14 cases   2.3%  median  107.1 h  Create Sales Order > Change Requested Delivery Date > Create Delivery > Post Goods Issue > Create Invoice
      6.   10 cases   1.7%  median   38.9 h  Create Sales Order > Change Price > Change Price > Create Delivery > Post Goods Issue > Create Invoice
      12 variants in total
    
    Touchless rate 63.3% (380 of 600 cases have none of: Set Credit Block, Release Credit Block, Change Price, Change Requested Delivery Date, Reject Sales Order)
    
    Rework (cases where a step happens more than once)
      Change Price                       15 cases
      Release Credit Block                6 cases
      Set Credit Block                    6 cases
    
    Waiting time between steps (directly follows), by total hours
      Post Goods Issue > Create Invoice                           596x  median   10.3 h  total    6615 h
      Create Delivery > Post Goods Issue                          596x  median    3.2 h  total    5171 h
      Set Credit Block > Release Credit Block                     138x  median   24.1 h  total    4112 h
      Create Sales Order > Create Delivery                        380x  median    0.7 h  total    2168 h
      Change Requested Delivery Date > Create Delivery             21x  median   49.1 h  total    1078 h
      Create Sales Order > Change Price                            47x  median    8.4 h  total     782 h
    
    Detours: steps only some cases have, and the extra time those cases take
      step                             cases  median with  without  extra hours
      Release Credit Block               155       39.4 h   18.6 h         3232
      Set Credit Block                   159       37.8 h   18.7 h         3042
      Change Requested Delivery Date      21      109.2 h   24.6 h         1778
      Change Price                        76       36.8 h   23.9 h          984
      Reject Sales Order                   4        7.8 h   26.6 h          -75
    
    Top delay pattern: Set Credit Block > Release Credit Block, 138 times, median 24.1 h, total 4112 h
      by weekday:
        Mon            28x  median   24.1 h  share of hours  15.8%
        Tue            17x  median   16.9 h  share of hours   8.2%
        Wed            22x  median   15.6 h  share of hours  10.1%
        Thu            22x  median   18.0 h  share of hours   9.1%
        Fri            17x  median   63.3 h  share of hours  24.5%
        Sat            18x  median   49.2 h  share of hours  22.1%
        Sun            14x  median   26.4 h  share of hours  10.3%
      by channel:
        10             18x  median   24.6 h  share of hours  11.3%
        20            120x  median   23.1 h  share of hours  88.7%
      by item_group:
        pumps          56x  median   24.8 h  share of hours  44.9%
        spare parts    82x  median   22.5 h  share of hours  55.1%
    
    Wrote the process map to unit14/mining/process_map.md (8 edges)
  1. Read it from top to bottom, as an FDE would:

    Section What it tells you here
    Variants The happy path covers 63% of orders. Variant 2, the credit block, is the next biggest and takes about a day longer
    Touchless rate 63%: the same 380 orders. Every other order had at least one person step in
    Rework 15 orders had their price changed twice. Six were blocked twice: a price fixed after release raised the value
    Waiting time The two biggest edges are normal work and the nightly billing run. The third, block to release, is a queue
    Detours The credit block costs about 3,200 extra hours a quarter, more than any other detour
    Drill-down Blocks set Friday to Sunday hold 57% of the hours. Distributor orders hold 89%
  2. Open unit14/mining/process_map.md. It holds a Mermaid flowchart of every step pair used by at least 5% of cases, labelled with the count and median hours. GitHub shows it as a diagram. In VS Code, the preview needs a Mermaid extension; or paste the code block into mermaid.live.

An "empty" result is also informative. If the detour list shows only negative extra hours, no optional step slows cases down, and the script stops with No detour adds time in this log.

Step 6: Draft the problem frame

  1. Run:

    python unit14/mine_o2c.py frame
  2. You should see (after the pm4py box):

    Pattern: Set Credit Block > Release Credit Block, median 24.1 h over 138 cases
    Wrote the draft frame to unit14/framing/credit_block_wait.txt
    Next: python unit14/problem_framer.py lint unit14/framing/credit_block_wait.txt
  3. Open unit14/framing/credit_block_wait.txt. The key lines are:

    METRIC: Median hours from 'Set Credit Block' to 'Release Credit Block'
    BASELINE: 24.1 | system, event log 2026-07-01 to 2026-10-05, 138 cases
    TARGET: 12.1
    ADDRESSABLE: 0.7 | estimate, share of the wait that is queue time, not decision work; time it while shadowing
    BENCHMARK: A Friday afternoon and Monday 08:00 review slot (49 of 138 waits start Friday to Sunday)

    The baseline is labelled system: it was counted from data. The addressable share is labelled estimate, because the log can't tell queue time from decision work. Only shadowing can.

Step 7: Check the frame

  1. If you have problem_framer.py from the problem framing topic, run:

    python unit14/problem_framer.py lint unit14/framing/credit_block_wait.txt
  2. You should see:

    == unit14/framing/credit_block_wait.txt
      OK    Target asks for a 50% change (24.1 to 12.1 hours).
      OK    Ceiling: best case is 7.2; the target needs 71% of the addressable part.
      WARN  ADDRESSABLE is an estimate. Time real cases before you promise the target.
      -> FRAMED: 0 to fix, 1 to consider
    
    1 of 1 frames ready for the opportunity assessment.

    The warning is correct. It stays until someone times real reviews.

  3. Save your work:

    git add unit14/mine_o2c.py unit14/mining/ unit14/framing/ requirements.txt
    git commit -m "Unit 14: process mining on the order-to-cash log"

How the code works

Part of the script What it does
make_case and cmd_sample Generates one order's events from the course rules, with seed 42, and writes the CSV
load Reads the CSV with pandas and renames the three key columns with pm4py.format_dataframe, so pm4py knows the case, activity and time
pm4py.get_variants Groups cases by their sequence of activities and counts each variant
INTERVENTIONS Your definition of "touched". Change the list and the touchless rate changes with it
pm4py.get_rework_cases_per_activity Counts, per activity, the cases where it happens more than once
pairs Puts each event next to the following event in the same case and computes the wait
detours For each step in at most half the cases, compares median case duration with and without it
top_detour and chosen_pattern Picks the detour with the most extra hours, then the wait into or out of it with the most hours; or uses --pattern
Segment loop in cmd_analyze Splits that wait by weekday and by every case attribute with 2 to 12 values
write_map Writes the directly-follows edges as a Mermaid flowchart
cmd_frame Writes a draft frame in the problem framer's format, with the baseline labelled system

Use your own log

Any CSV with one row per event works. Name the columns with options:

python unit14/mine_o2c.py analyze --log path/to/events.csv --case order_id --activity step --time changed_at

Timestamps must be in a format pandas reads, such as 2026-09-21 07:00:00. Remove names and other personal data before the file reaches your laptop. To drill into a wait you choose, add --pattern "Step A > Step B", using names from the waiting-time list.

If something goes wrong

What you see What it means What to do
python: command not found or 'python' is not recognized Python isn't installed or isn't on the PATH Follow Step 1 of the computer setup topic, then open a new terminal
ERROR: No matching distribution found for pm4py==2.7.23.11 Python is older than 3.11 Install Python 3.11 or newer and recreate .venv
ModuleNotFoundError: No module named 'pandas' or 'pm4py' The library isn't installed in this environment Activate .venv (Step 1), then pip install -r requirements.txt
pip times out or shows ProxyError / SSL errors A company network or proxy blocks downloads Try another network, or ask IT for the proxy settings for pip
No event log at ... analyze ran before sample, or the path is wrong Run python unit14/mine_o2c.py sample from the course folder
Column 'order' not in ... Your own CSV uses other column names Pass --case, --activity and --time
No case goes directly from ... The --pattern names don't match Copy both names exactly from the waiting-time list
Numbers differ from this page The sample was edited, or a different script version Run sample --force and compare the first five lines of the CSV

This section needs no key, so a missing or wrong key can't be the cause of an error here.

The SAP way

SAP Signavio Process Intelligence

As of 1 November 2026, SAP's Signavio site describes Process Intelligence with pre-built connectors for SAP and non-SAP systems, ready-made process flows and metrics, SIGNAL queries, Process Atoms for conformance rules, data-driven simulation, AI-assisted root cause analysis and actions through SAP Build Process Automation. The page offers a free trial.

Getting data in runs through a process data pipeline. SAP Learning describes its parts: a connection to the source system, source data (which tables and columns), extraction with scheduling, pseudonymization and partitioning, transformation as SQL scripts that define business objects and data model tables, and a load into a process (SAP Learning). Value Accelerators provide transformation templates for common processes. The same lesson says they need the Data Modelling feature set enabled by a workspace administrator.

Analysis then uses SiGNAL, which SAP Learning calls SQL-oriented. It queries one table of cases with nested events; case_id, event_name and end_time are always present, and it reads data without changing it (SAP Learning). The detour comparison in this topic's script is the kind of question you would write in SiGNAL there.

SAP Signavio Process Insights

Process Insights is the prebuilt route for SAP ERP. SAP Learning lists SAP ECC 6.0 and SAP S/4HANA on-premise among its sources. The ST-PI and ST-A/PI plug-ins collect the data and send it over an encrypted HTTPS outbound connection to SAP BTP, where Process Insights runs (SAP Learning). It comes with more than 200 process performance indicators and correction recommendations.

What is new, and what is GA

Capability Status as SAP describes it Source
One experience for Process Insights and Process Intelligence Being brought together, May 2026 May 2026 release
Process Networks (object-centric view of orders, deliveries, invoices) Moved from beta to GA, May 2026 Same
AI-assisted root cause analysis; context analyzer for customer feedback Planned for GA in June 2026 at the time of the May post Same; confirm current status
Business process simulation in Process Intelligence Announced in the July 2026 release; no status stated July 2026 release
Process Consulting Agent, in the Process Transformation Assistant Phased general availability, October 2026 SAP News

Check the status for your edition and region before promising any of these to a client. Licensing is a contract question; the product pages show no prices.

Reading the data yourself on S/4HANA

When you build an event log outside Signavio, read through released interfaces. SAP's published release-state list (checked 1 November 2026) marks the tables CDHDR and CDPOS as not to be released for ABAP Cloud, with the classes CL_CHDO_READ_TOOLS and CL_CHDO_WRITE_TOOLS as successors. It lists the CDS view I_CHANGEDOCUMENTFORCHGDOCOBJ (change documents) and I_SDDOCUMENTMULTILEVELPROCFLOW (sales document flow) as released (SAP on GitHub). Which view a given extraction tool may call depends on your edition and authorizations.

For credit blocks, the analyst's decision happens in the Manage Documented Credit Decisions app, where blocked documents are released or rejected (SAP Learning). A credit-block event log in a real system comes from those decisions and the order's status changes. The order-to-cash topic traces the documents.

Build vs. SAP

Situation Use Why
One-off discovery on a single process, data export allowed pm4py on an extract Free, fast, enough for a hypothesis and a baseline
The customer already licenses SAP Signavio Process Insights first, then Process Intelligence Prebuilt indicators and connectors; no extraction to build
ECC customer before migration Process Insights on ECC, or an extract Mining now gives a baseline to compare after the move
Ongoing monitoring of the KPI after go-live Process Intelligence, or a scheduled extract The metric is rerun every month by the business, not by you
Many objects per case (orders, deliveries, invoices) An object-centric tool, such as Process Networks A single case ID causes convergence and divergence
Shipping process mining inside a client product Signavio, or check pm4py's AGPL and commercial license License terms decide, not the algorithm

Architecture view

Where it sits. Event data leaves the system that records it and lands where the analysis runs. Nothing writes back to SAP.

flowchart LR
  S4[S/4HANA CP10<br/>orders, change docs,<br/>document flow] -->|extraction, read only| PI[Signavio<br/>process data pipeline]
  ECC[ECC CP20] -->|ST-A/PI plug-in| PS[Process Insights<br/>on SAP BTP]
  S4 -->|released views,<br/>pseudonymized extract| X[CSV extract]
  X --> PM[pm4py on<br/>FDE laptop]
  PI --> F[Findings and baseline]
  PS --> F
  PM --> F
  F --> FR[Problem frame]

Non-functional requirements.

Requirement Typical target What breaks if missed
Personal data Pseudonymized or removed before export Data protection breach; workers' council objections
Data volume One process, one quarter, all cases Sampling hides rare but costly variants
Freshness Daily or weekly for monitoring; once for discovery Stale baseline after a process change
Source load Extraction outside peak hours Slow dialog response during month-end
Repeatability The same query reruns after go-live No proof the change worked

Integration and identity. Extraction runs as a technical user with read-only access to the needed tables or views, not under a person's account. Event logs carry user IDs; pseudonymize them unless the analysis needs roles. Findings that name individuals' speed are a works-council topic in many countries, so mine by role and team.

The decision to record (short ADR form from the design-doc topic):

  • Context: Calder (fictional) wants to know why credit-blocked distributor orders are late; Signavio isn't licensed; the extract is one quarter of CP10 orders.
  • Options: A a pm4py analysis on a pseudonymized extract; B a Process Insights trial or analysis; C a Process Intelligence project.
  • Decision: A for discovery, with B requested in parallel to cross-check the baseline.
  • Consequences: the FDE owns the extraction query and its definition; the baseline must be rerun the same way after go-live; any ongoing monitoring moves to C or to a scheduled job.

What a design review will ask.

  1. What is the case, and which events converge or diverge under that choice?
  2. Which fields are in the extract, and how is personal data removed?
  3. Which waits are by design, and how did you exclude them?
  4. How was the finding confirmed beyond the log?
  5. Who reruns the baseline after go-live, and with what query?

Production concerns

  • Authorizations. Reading change documents across a whole process needs broad read access. Use a technical user scoped to the needed views, and log what was extracted and when.
  • Data protection. Event logs hold user IDs, customer names and amounts. Export only the columns the analysis needs. SAP Learning lists pseudonymization as part of Signavio extraction; do the same in your own extracts.
  • Works councils and trust. Process mining can look like monitoring people. Agree up front that results are by process and role, not by person.
  • Evaluation. A finding is a hypothesis. Check it against interviews and a sample of cases you open in SAP. If the log says "Friday blocks wait 63 hours", open five Friday blocks and see.
  • Cost. pm4py is free to run but licensed under the AGPL. Signavio is a licensed product; ask what your contract covers before planning around it.
  • Operations. A one-off analysis is cheap. A monitored KPI needs an owner, a schedule and a stable definition.
  • Clean core. Mining only reads. Use released views and standard extractors, not custom code in the SAP system.

Pitfalls

  • Treating all waits as waste. Nightly billing and payment terms are waits by design. Separate them before ranking.
  • Mining the wrong case. Delivery-level problems look strange at order level. Choose the case closest to the decision you want to change.
  • Trusting activity names. A change document entry is not an activity until you map it. Check the mapping per system and per configuration.
  • Ignoring time zones. Plants in Rockford and Augsburg log in different zones. Convert before you compute waits.
  • Reading medians alone. A median hides the long tail. Look at the total hours and the slowest cases too.
  • Short periods. One month may miss quarter-end. Mine at least a quarter.
  • Stopping at the map. A process map impresses in a meeting. It isn't a finding until it names a number, a cause and an owner.

Exercise

Frame another detour: orders waiting for a price fix. It belongs to customer service, not credit, and unlike a customer's date change, Calder controls its cause. Your frame feeds the opportunity assessment next in Unit 14.

  1. Drill into the wait before the price change:

    python unit14/mine_o2c.py analyze --pattern "Create Sales Order > Change Price"
  2. In the drill-down, note the median (8.4 h), the share of hours from channel 20, and the weekdays with the longest waits.

  3. Draft the frame:

    python unit14/mine_o2c.py frame --pattern "Create Sales Order > Change Price" --actor "Customer service agent" --owner "Head of Customer Service" --ask "Can AI fix missing prices on spare-part orders?" --out price_fix_wait.txt
  4. Open unit14/framing/price_fix_wait.txt. The guardrail, constraint, out-of-scope and benchmark lines are written for credit blocks. Rewrite each one for customer service. For example:

    GUARDRAIL: Orders invoiced at a wrong price do not increase
    CONSTRAINT: Read-only access to SAP under the customer service agent's own authorizations
    CONSTRAINT: The customer service agent confirms every price before it is saved
    OUT: Changing prices in SAP without a person's approval
    OUT: Changing price conditions or price lists
    BENCHMARK: Maintain the missing aftermarket prices for the spare parts most often ordered
  5. Check that no line still mentions credit or release.

    Windows (PowerShell):

    Select-String -Path unit14/framing/price_fix_wait.txt -Pattern 'credit|release'

    macOS/Linux:

    grep -iE 'credit|release' unit14/framing/price_fix_wait.txt

    No output means you are done with this step.

  6. Lint it:

    python unit14/problem_framer.py lint unit14/framing/price_fix_wait.txt
  7. Add one line at the top of the file, starting with #, saying which of these you think fits first: a master data fix, a rule, or AI. Give the reason from the drill-down.

  8. Commit:

    git add unit14/framing/price_fix_wait.txt
    git commit -m "Unit 14: price-fix wait framed from process mining"

Done when price_fix_wait.txt shows FRAMED in the lint, the search for credit or release finds nothing, and the first line says which fix comes first and why.

Going further

For readers who want the theory and standards behind event logs and object-centric mining.

  • An Event Data Extraction Approach from SAP ERP for Process Mining (Berti, Park, Rafiei, van der Aalst). How change tables and document flow become events, and why a single case notion causes convergence and divergence. Read it before you write your own extractor.
  • OCEL 2.0 Specification. The object-centric event log standard: events related to many objects, object changes over time, and object-to-object relations, in SQLite, XML or JSON. It is the format to reach for when orders, deliveries and invoices must be analyzed together.
  • pm4py rework source. A short, readable example of how a process mining measure is defined in code. Reading the source of the measures you report is the fastest way to know exactly what a number means.

Three ideas worth exploring from these:

  • Directly follows vs. eventually follows. This topic's waits use directly-follows pairs: the next event. A credit block followed by a price change and then a release has no direct block-to-release pair, so it drops out of the 138 waits. Eventually-follows relations keep it, at the cost of counting overlapping spans.
  • Discovery algorithms. A process map of directly-follows edges is the simplest model. Algorithms such as the inductive miner produce models with explicit choices and loops, which conformance checking needs.
  • Object-centric drill-downs. With OCEL, "orders whose deliveries split" is a query, not a data-preparation project. Process Networks in Signavio is SAP's object-centric view.

Check yourself

Pick one answer for each question. The explanation appears after you choose.

1Why does the choice of case ID matter so much when building an event log from SAP?

Answer: B. One order can have several deliveries and one invoice can bill several orders. Forcing all of them onto one case copies events into several cases or creates fake loops, which distorts variants and rework.

2In the script, how does the detour comparison decide which step costs the most time?

Answer: C. detours compares median case duration with and without each optional step and multiplies the difference by the number of cases that took it. Total hours per step pair, the most tempting choice, mostly finds normal work such as the nightly billing run.

3Post Goods Issue to Create Invoice has the most total hours in the log. Why isn't it the problem to frame?

Answer: A. Every order waits until the 22:00 billing run, so the wait is a scheduling choice that applies to all cases. The detour comparison exists to separate such waits from the friction only some cases suffer.

4The drafted frame labels the baseline system but the addressable share estimate. Why?

Answer: A. The median wait is counted from timestamps, so it is a system number. How much of that wait is queue versus the analyst's actual work isn't recorded, so it stays an estimate until someone times real reviews.

5You want to build an event log from change documents on S/4HANA. What does SAP's release-state list suggest?

Answer: D. The list marks CDHDR and CDPOS as not to be released for ABAP Cloud and lists the change-document CDS view as released. Reading through released interfaces keeps the core clean.

6A client wants process mining built into a product they sell. You prototyped with pm4py. What should you raise first?

Answer: C. pm4py is licensed under the AGPL, and its maintainers offer a commercial license. Shipping it inside a product is a legal decision, which is why the Build vs. SAP table lists licensing as the deciding factor there.

7The log shows Friday credit blocks wait 63 hours. What do you do before proposing a solution?

Answer: C. A mined pattern is a hypothesis. Opening real cases and talking to the analysts confirms the cause, such as a once-a-day review, before anyone builds anything.

8Which status statement about SAP Signavio is accurate as described in this topic?

Answer: D. SAP News on 26 October 2026 describes a phased general availability for the Process Transformation Assistant, which includes the Process Consulting Agent. The May post only planned root cause analysis for June, and it moved Process Networks to GA.

Sources

Sign in to track your progress

We'll email you a one-time sign-in link. No password needed.

or

Tell us a little about you

Optional, every field. It helps us pitch answers to your questions at the right level and decide which topics to write next. It is never shown publicly, and you can change or clear it anytime from the account menu.

SAP areas you work in