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.

Fit-to-standard and extension choices: where each requirement should live

Decide for each requirement whether it needs configuration, a process change, a standard feature, an extension or nothing, and defend the call with evidence.

Updated Nov 5, 2026Foundational 9 minDeep 38 min
Foundational layer · 9 min read

The 60-second version

Every SAP program collects hundreds of requirements. Each one has to land somewhere. There are only six places it can go:

  1. Configuration. SAP already does it; someone sets it up.
  2. A process change. SAP does it differently, and the business adopts SAP's way.
  3. A standard feature not yet in scope. SAP has an app or product for it that nobody switched on.
  4. A side-by-side extension. Something new is built next to SAP, on SAP's cloud platform.
  5. An on-stack extension. Something new is built inside SAP, using the doors SAP keeps stable.
  6. Nothing. The requirement isn't needed, and the program says no.

Fit-to-standard is SAP's method for sorting requirements into those places. Experts show the business how standard SAP runs a process, and the business says what truly doesn't fit. Only those gaps go on the list of things to build. The goal is a short list, because everything built has to be tested and paid for at every upgrade for as long as it lives.

This topic shows how a solution architect makes that call, using Calder Pumpworks, the fictional pump maker this unit follows. Ten real-looking Calder requests get one decision each.

Why it matters to the business

The decision looks small, one request at a time. Added up, it decides what the system costs to run.

  • Every extension is a running cost. SAP's own training warns that each change to a standard process adds maintenance work at every release upgrade (SAP Learning). A program that builds 300 extensions has signed up to retest 300 things, every upgrade, for a decade.
  • The way it is built matters as much as whether it is built. SAP rates extensions from level A, built only on interfaces SAP keeps stable, to level D, such as changing SAP's own code or writing into SAP's tables (SAP Learning). Level D work can break at any upgrade and makes the next one slower and dearer.
  • Old habits hide in requirements. Many requests are "make it work like the old system". At Calder, the German company CP20 is moving from ECC into the US company's S/4HANA system. Augsburg's buyers ask to keep a program that writes delivery dates straight into purchase orders. It works today; in the new system, it is the riskiest kind of change.
  • Saying no saves the most money. A report nobody ran last year costs nothing to retire. Rebuilt, it costs a build plus every future upgrade.

A concrete case: in order-to-cash, Calder's sales director wants to release credit-blocked orders himself, by phone. Standard SAP already sends blocked orders to a credit analyst, who releases them in a standard app. The right answer is a process change, not a new approval screen, and it also keeps the person who sells apart from the person who approves credit.

How SAP does it

As of October 2026, SAP supports this decision with a method, a few tools and a rating scale:

  • SAP Activate, SAP's implementation method, runs the fit-to-standard analysis in its Explore phase. Experts demonstrate standard processes, the team identifies the "delta" requirements that don't fit, and the product owner validates each one before it joins the backlog for the build phase (SAP Learning).
  • SAP Signavio Process Navigator supplies the standard process flows and test scripts used in the workshops, and SAP Cloud ALM records the requirements and tracks them through build and test (SAP Learning).
  • Three ways to extend when a gap is real: key user tools (apps that add fields, rules, forms and reports without programming), developer extensibility with ABAP Cloud (code inside S/4HANA that uses only released SAP objects), and side-by-side apps on SAP Business Technology Platform (BTP). Private edition and on-premise systems also allow classic extensibility, which can change any SAP object and is the least stable (SAP Learning).
  • Clean core levels A to D give leaders one scale for all of it. SAP calls the model "a shared vocabulary" for assessing a landscape, and its messaging recommends an "SAP BTP-first strategy" (SAP News, August 2025).
  • A Solution Standardization Board is SAP's suggested governance body: it reviews every proposed deviation from standard and records why it was accepted, so the extension can be removed when the standard catches up (SAP Learning).

The workshop technique itself (who attends, how to run the room) belongs to the solution architect's job and is covered in the solution architect on an SAP program. This topic is about the decision each workshop feeds.

A decision guide: where each requirement should live

Ask the questions in this order. The first "yes" decides.

# Question If yes Calder example
1 Is there evidence the business needs it: a law, a contract, a measured KPI, actual usage? If no: decline it, and record why Augsburg's open-PO aging report: zero runs in 14 months
2 Does standard SAP do it with configuration? Configuration Approval of purchase orders above 25,000 USD
3 Can the business adopt SAP's standard way instead? Process change Credit releases by the analyst, not by phone
4 Is there a standard SAP feature or product nobody has switched on? Add it to scope Capacity planning for the pump test bench
5 Do people outside S/4HANA use it, or must it change on its own schedule? Side-by-side on SAP BTP Order status for distributors
6 Is it a small last-mile change: a field, rule, form or report? Key user extension Test certificate number on orders
7 Otherwise Developer extension (ABAP Cloud) Monthly intercompany price calculation

Two rules sit across the table. First, every extension is rated A to D, and anything below level B needs the standardization board's written approval. Second, "the old system did it" is not evidence. Usage data, a regulation or a number the business is measured on is.

Questions to ask

  1. How many requirements came out of fit-to-standard, and what share became extensions? Which were declined, and on what evidence?
  2. Who owns the final call on a gap: the product owner, a design authority, a standardization board?
  3. What clean core level is each planned extension, and who approved anything below level A?
  4. Did we collect usage data on the old system's custom code before deciding what to rebuild?
  5. For each side-by-side extension, who runs it, and what does it cost per year on BTP?
  6. Which "gaps" are really training gaps: the business hasn't seen how SAP does it yet?
  7. When SAP's standard catches up with an extension, who notices, and how do we retire it?

Common misconceptions

  • "Fit-to-standard means no customization." It means the gaps are real and justified. Some extensions are worth their cost; the point is to know which ones.
  • "Side-by-side is always cleaner." It is upgrade-stable, but it is a second application to run, secure and pay for. A key user field is cheaper than a BTP app for a last-mile change.
  • "Classic ABAP is forbidden in private edition." It is allowed, and SAP rates extensions on classic APIs as level B, low risk. What SAP calls not recommended is level D: modifications and writes into SAP tables.
  • "The partner decides what gets built." The business owns the requirements and their cost. The partner and the architect advise; a product owner or board decides.
  • "We can clean up later." Every upgrade before the clean-up costs extra testing, and later rarely comes.

Key terms

All course terms, A to Z, in the glossary

  • Fit-to-standard: the SAP Activate step that compares standard SAP processes with the business's needs, so only real gaps are built.
  • Delta requirement (gap): something standard SAP doesn't do that the business needs.
  • Fit-gap register: the list of requirements with their decision, rationale and owner.
  • Configuration: settings that change how standard SAP behaves, without code.
  • Key user extensibility: SAP apps that let trained business users add fields, rules, forms and reports.
  • Developer extensibility (ABAP Cloud): code inside S/4HANA restricted to SAP's released objects.
  • Side-by-side extension: an app outside S/4HANA, usually on SAP BTP, that uses S/4HANA through APIs or events.
  • Clean core level: SAP's A-to-D rating of how safely an extension survives upgrades.
  • Solution Standardization Board: the group that approves and records deviations from standard.

Check yourself

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

1A department asks to rebuild a custom ECC report in S/4HANA. What is the first thing to establish?

Answer: D. The decision guide starts with evidence of need. A report with no recorded use is declined, which costs nothing; choosing how to build it only matters once the need is proven.

2Calder's sales director wants to release credit-blocked orders himself, by phone. Standard SAP routes them to a credit analyst. What is the right call?

Answer: A. Standard SAP already handles the need, and adopting it keeps selling and credit approval with different people. The need is real, so declining is wrong; building an app would add cost to undo a sound control.

3Why does SAP training warn against changing standard processes during fit-to-standard?

Answer: C. Each deviation has to be retested and maintained whenever SAP upgrades the system. That is why workshops keep only the requirements that are truly needed.

4Distributors want to check order status online, and they have no SAP users. Where should this requirement live?

Answer: D. The users are outside S/4HANA, which points to a side-by-side app that reads S/4HANA through stable APIs. A modification would be clean core level D, the riskiest kind of change.

5What does a clean core level tell an executive about an extension?

Answer: B. Levels A to D rate how an extension is built, from released, stable interfaces at A to modifications and table writes at D. The lower the level, the more each upgrade can break it.

6Who should own the final decision on whether a gap is built?

Answer: D. In SAP Activate the product owner validates deltas before they enter the backlog, and SAP suggests a standardization board that approves and records deviations. Partners and architects advise; the business owns the cost.
Deep layer · 38 min read

Mental model

Treat every requirement as a liability with a lifetime, not as a feature request. Configuration is a liability SAP shares with you: it runs on code SAP maintains through every upgrade, so you regression-test your processes but nothing of yours breaks underneath. An extension is a liability you carry alone: you build it once and retest it at every upgrade until you delete it. The fit-gap decision is therefore a cost decision with a long tail, and it has a strict order:

  1. Is the requirement real? If not, the liability is zero.
  2. Can SAP carry it (configuration, a process change, a standard feature)? Then SAP carries most of it.
  3. Only if both fail, build, and build at the highest clean core level available, because the level sets the size of the tail.

Two numbers make the tail visible: how many upgrades the extension will live through, and how much each one costs to retest it. In this topic's cost model, a level A extension costs half a day of retesting per upgrade and a level D one costs eight days; those are course inputs, not SAP figures. The real difference is worse than any estimate: a program that writes into SAP tables can fail outright when SAP changes the table underneath it.

How it works

Fit-to-standard inside SAP Activate

SAP Activate puts the fit-to-standard analysis in the Explore phase. As SAP Learning describes it (opened 28 October 2026):

  • Preparation. Scope comes from the earlier phases (for Public Edition, the Digital Discovery Assessment). The business answers the Business-Driven Configuration Questionnaire, which supplies configuration values. Experts download test scripts from SAP Signavio Process Navigator and run them in a starter system, a system preconfigured with SAP Best Practices (SAP Learning).
  • Workshops. Experts execute each in-scope process in the starter system, the business judges the fit, and requirements are recorded. The workshops are time-boxed so the whole scope is covered. In private cloud projects, the configuration step also covers WRICEF objects (workflows, reports, interfaces, conversions, enhancements and forms); in Public Edition they are less common because there is less to extend (SAP Learning).
  • Validation and backlog. The product owner validates the deltas, prioritizes the backlog, and only the top items go to design workshops; the rest wait for refinement (same source).
  • Tracking. In SAP Cloud ALM, a requirement is created from a solution process, approved, refined into user stories, and grouped into a feature that creates or is assigned transports; test cases attach to the requirement or its stories (SAP Learning).

The architect's job sits between the workshop and the backlog: turning "we need X" into a classified, rated, costed decision.

Classifying a gap

Before deciding, classify each gap along three axes. The register needs all three.

Axis Values Why it matters
Evidence Law or regulation, contract, measured KPI, usage data, none No evidence, no build. "The old system did it" isn't evidence
Object type Field, rule, form, report, workflow, interface, conversion, new app Points to the tool: fields and forms to key user apps, new apps to side-by-side or ABAP Cloud
Coupling Who uses it (S/4HANA users or outsiders); must it change in the same transaction as SAP data? Outsiders and loose coupling point side-by-side; same-transaction logic points on-stack

Interfaces and conversions are gaps too, but they have their own design homes: integration architecture in Unit 5B, and data migration later in this unit.

The decision tree

flowchart TD
  R[Requirement] --> E{Evidence of need?}
  E -- no --> N[Nothing: decline,<br/>record evidence]
  E -- yes --> C{Configuration<br/>covers it?}
  C -- yes --> CF[Configuration]
  C -- no --> P{Can the business<br/>adopt standard?}
  P -- yes --> PC[Process change]
  P -- no --> F{Standard feature<br/>not yet in scope?}
  F -- yes --> SF[Standard feature]
  F -- no --> O{Users outside S/4HANA<br/>or own lifecycle?}
  O -- yes --> SBS[Side-by-side<br/>on SAP BTP]
  O -- no --> L{Last-mile:<br/>field, rule, form, query?}
  L -- yes --> KU[Key user]
  L -- no --> DEV[Developer:<br/>ABAP Cloud]
  SBS --> LV[Rate A to D]
  KU --> LV
  DEV --> LV
  LV --> SSB{Below level B?}
  SSB -- yes --> B[Board approval<br/>or redesign]
  SSB -- no --> BL[Backlog]

The order of questions 5 to 7 follows SAP's guidance. SAP's training says to exhaust key user options first and move to side-by-side for more complex extensions, and to check the SAP Store and the SAP Certified Solutions Directory for a partner solution before building anything fully custom (SAP Learning). SAP's older extensibility training adds the criteria that split on-stack from side-by-side: who the users are, how tightly the data must change together, the kind of scenario, and whose lifecycle the extension follows (SAP Learning). That training still lists ABAP custom development as on-premise only, which no longer matches private edition, so use its criteria, not its list of tools.

The three extension options, as of October 2026

Option What it is Available in Typical use
Key user Web-based SAP Fiori apps: UI adaptation, Custom Fields, Custom Business Logic, Custom Business Objects, Custom CDS Views, Custom Analytical Queries, KPIs and Custom Forms All SAP S/4HANA deployment options (SAP Learning) A field on an order, a validation, a form, a report on existing data
Developer (ABAP Cloud) ABAP restricted to released SAP objects, enforced by the "ABAP for Cloud Development" language version Public Edition (from 2208); private edition and on-premise from SAP S/4HANA 2022; not ECC (SAP Community FAQ) Logic and apps that work on core data in the same transaction
Side-by-side Apps on SAP BTP built with SAP Build (low-code) or SAP Business Application Studio with CAP, calling S/4HANA through APIs and events Any edition with released APIs (SAP Learning) Users outside SAP, mobile or freestyle UIs, logic with its own release cycle
Classic Any ABAP, including access to any SAP object Private edition and on-premise only; least stable (same source) Legacy code after a conversion; new work only with board approval

How side-by-side apps are built is covered in CAP and side-by-side extensions; ABAP Cloud and RAP in ABAP Cloud and RAP for AI.

Clean core levels

SAP's level model replaced the earlier three-tier model; SAP's training says the tier model is sunset (SAP Learning).

Level Built on Upgrade risk
A Released APIs and extension points with stability contracts: key user, ABAP Cloud, side-by-side on BTP Lowest; matches how Public Edition is extended
B Classic APIs, such as BAPIs, documented and generally upgrade-stable Low; the next option when no released API exists
C SAP internal objects, such as reading tables not meant for customers Can change without notice; manage with SAP's changelog
D Modifications, writes to SAP tables, implicit enhancements Highest; SAP recommends remediation

An extension takes the level of the lowest technique it uses. One direct table write makes an otherwise clean app level D.

Where the level comes from in practice. SAP publishes the state of its objects in the Cloudification Repository on GitHub. The ABAP test cockpit (ATC) reads those files to check custom code, and SAP's clean core check, available since August 2025, uses the ATC checks "Usage of APIs" and "Allowed Enhancement Technologies" (SAP on GitHub). In the private cloud file read for this topic, the purchase order tables EKKO and EKPO are notToBeReleased, with the released CDS views I_PurchaseOrderAPI01 and I_PurchaseOrderItemAPI01 as successors; BAPI_PO_CHANGE is listed as a classicAPI.

The long-term cost of custom code

An extension's cost over its life is roughly:

build + years × (run + upgrades per year × retest effort at its level)

The retest effort is the part programs underestimate. Public Edition upgrades twice a year; private edition customers apply feature packs and upgrades on their own plan within SAP's maintenance rules (Landscape and deployment). For code that already exists, SAP's guidance is to measure before deciding: run the ABAP Call Monitor (SCMON) or Usage and Procedure Logging (UPL) in production for 6 to 18 months including a year-end close, aggregate with SUSG, and let the Custom Code Migration app find unused code and generate a transport to delete it (SAP Learning).

Saying no with evidence

A "no" survives a steering committee when it carries four things:

  1. The need test. Usage data, or the absence of a law, contract or KPI behind the request.
  2. The standard alternative, shown. The test script run in the starter or sandbox system, with the business user at the keyboard.
  3. The rating. The clean core level of the requested design, from ATC or the Cloudification Repository, not opinion.
  4. The cost. The five-year cost of the requested design next to the alternative.

Then record it: the decision, the evidence and who agreed, in the register and, for anything contested, in an architecture decision record.

Build it yourself

Before you start: finish Set up your computer for this course and Set up for Unit 1B.

You will build Calder's fit-gap register: ten requirements from order-to-cash, procure-to-pay and plan-to-produce, each run through the decision tree above. The script prints the register, explains the path any requirement took, compares five-year costs of extensions as decided and as first requested, and can re-check the clean core inputs against SAP's current Cloudification Repository files.

flowchart LR
  Q[REQUIREMENTS<br/>workshop answers] --> S[fit_gap.py]
  T[TREE<br/>decision order] --> S
  A[API_STATES<br/>from SAP's repository] --> S
  S --> M[calder_fit_gap.md<br/>register]
  S --> X[--explain<br/>decision path]
  S --> C[--cost<br/>five-year cost]
  S -.->|--online| G[SAP files<br/>on GitHub]

What you need

  • The course folder orchestrate-course with its .venv, from the setup topics.
  • No account and no key. The script uses only Python's built-in modules, so requirements.txt doesn't change.
  • Network access only for the optional --online step, which downloads about 14 MB from GitHub.
  • Cost: free. Time: about 30 minutes.

Step 1: Open your course folder and turn on the virtual environment

  1. Open VS Code, then File > Open Folder and choose orchestrate-course.
  2. Open a terminal with Terminal > New Terminal.
  3. Turn on the virtual environment:
    • Windows (PowerShell):

      .venv\Scripts\Activate.ps1
    • macOS / Linux:

      source .venv/bin/activate

You should see (.venv) at the start of the prompt. The unit1b folder should already exist from the setup topic.

Step 2: Create the script

  1. In VS Code, create a new file, paste the script below, and save it as fit_gap.py inside the unit1b folder.
"""Build Calder Pumpworks' fit-gap register: ten requirements, one decision each.

Calder Pumpworks is a fictional company. Requirements, evidence, effort days,
day rate and upgrade counts are course labels and workshop inputs, not SAP
figures. The API states in API_STATES come from SAP's Cloudification
Repository on GitHub (files read on 28 October 2026); --online checks them
against the current files.

Run it from your course folder:
  python unit1b/fit_gap.py                  the register, written to unit1b/calder_fit_gap.md
  python unit1b/fit_gap.py --explain R07    the path one requirement takes through the decision tree
  python unit1b/fit_gap.py --cost           five-year cost of each extension, as decided and as requested
  python unit1b/fit_gap.py --online         re-check the API states against SAP's current files
The script needs no account and no key. Only --online uses the network.
"""
import argparse
import json
import sys
import urllib.request
from pathlib import Path

REGISTER = Path("unit1b") / "calder_fit_gap.md"
CR_BASE = "https://raw.githubusercontent.com/SAP/abap-atc-cr-cv-s4hc/main/src/"
CR_FILES = ["objectReleaseInfo_PCELatest.json", "objectClassifications_SAP.json"]

# ------------------------------------------------- SAP objects and their state
# State of each object in SAP's Cloudification Repository (private cloud file
# and classic API file, read 2026-10-28). "write" marks objects an extension
# would change rather than read.
API_STATES = {
    "I_SALESORDER": "released",
    "I_SALESORDERITEM": "released",
    "I_PURCHASEORDERAPI01": "released",
    "I_PURCHASEORDERITEMAPI01": "released",
    "I_PURCHASEORDERTP_2": "released",
    "I_PURCHASINGINFORECORDTP": "released",
    "I_SLSPRCGCONDITIONRECORDTP_2": "released",
    "BAPI_PO_CHANGE": "classicAPI",
    "EKPO": "notToBeReleased",
    "EKKO": "notToBeReleased",
}

# Clean core level for one use of one object (SAP's levels A to D).
def level_of(obj: str, mode: str) -> str:
    state = API_STATES.get(obj, "unknown")
    if state == "released":
        return "A"
    if state == "classicAPI":
        return "B"
    if state in ("notToBeReleased", "noAPI", "unknown"):
        return "D" if mode == "write" else "C"
    return "C"

# ------------------------------------------------------- the decision tree
# Each question is asked in order; the first "yes" decides.
TREE = [
    ("evidence", "Is there evidence the business needs it (law, contract, KPI, usage)?", "Nothing: decline", False),
    ("config", "Does standard SAP do it with configuration?", "Configuration", True),
    ("adopt", "Can the business adopt the standard process instead?", "Process change", True),
    ("standard_feature", "Is there a standard SAP feature or product not yet in scope?", "Standard feature", True),
    ("outside_users", "Do people outside S/4HANA use it, or must it run on its own lifecycle?", "Side-by-side (SAP BTP)", True),
    ("last_mile", "Is it a last-mile change: a field, rule, form or query?", "On-stack: key user", True),
]
FALLBACK = "On-stack: developer (ABAP Cloud)"

# ------------------------------------------------------- Calder's requirements
# Answers are workshop inputs. "objects" lists (object, read/write) for the
# design as decided; "requested" for the design the business first asked for.
REQUIREMENTS = [
    {"id": "R01", "process": "O2C", "text": "Hold orders when a customer is over its credit limit",
     "evidence": True, "config": True, "why": "SAP Credit Management blocks the order; analysts release it"},
    {"id": "R02", "process": "O2C", "text": "Pump test certificate number on order item and delivery note",
     "evidence": True, "config": False, "adopt": False, "standard_feature": False, "outside_users": False,
     "last_mile": True, "objects": [("I_SALESORDERITEM", "read")], "build_days": 3,
     "why": "A custom field plus a form change; key user apps, no code"},
    {"id": "R03", "process": "O2C", "text": "Distributors see order status and dates without SAP users",
     "evidence": True, "config": False, "adopt": False, "standard_feature": False, "outside_users": True,
     "objects": [("I_SALESORDER", "read"), ("I_SALESORDERITEM", "read")], "build_days": 25,
     "why": "Users outside S/4HANA; a small app on BTP reading released APIs"},
    {"id": "R04", "process": "O2C", "text": "Sales director releases credit blocks by phone",
     "evidence": True, "config": False, "adopt": True,
     "why": "The analyst releases in the standard app; keeps duties separate"},
    {"id": "R05", "process": "P2P", "text": "Plant manager approves POs above 25,000 USD",
     "evidence": True, "config": True, "why": "Flexible workflow with a total net amount condition"},
    {"id": "R06", "process": "P2P", "text": "Rebuild CP20's custom report ZMM_OPEN_PO_AGING",
     "evidence": False, "why": "Zero runs in 14 months of usage data, a year-end included"},
    {"id": "R07", "process": "P2P", "text": "Keep Augsburg's program that writes dates into PO items",
     "evidence": True, "config": False, "adopt": True,
     "requested": [("EKPO", "write")], "requested_days": 4,
     "why": "Buyers change dates in standard PO apps; the old program is level D"},
    {"id": "R08", "process": "P2P", "text": "Monthly intercompany price for CP-IMP200 at cost plus 8%",
     "evidence": True, "config": False, "adopt": False, "standard_feature": False, "outside_users": False,
     "last_mile": False,
     "objects": [("I_SLSPRCGCONDITIONRECORDTP_2", "write"), ("I_PURCHASINGINFORECORDTP", "write")],
     "requested": [("BAPI_PO_CHANGE", "write")], "requested_days": 10, "build_days": 12,
     "why": "Logic on core data, run as a job; ABAP Cloud on released objects"},
    {"id": "R09", "process": "Plan", "text": "Final inspection at goods receipt for ES-300 only",
     "evidence": True, "config": True, "why": "Inspection type set in the ES-300 material master only"},
    {"id": "R10", "process": "Plan", "text": "See TEST1 load against capacity and the maintenance downtime",
     "evidence": True, "config": False, "adopt": False, "standard_feature": True,
     "why": "Capacity evaluation and the Capacity Scheduling Board; add to scope"},
]

# Course inputs for the cost of ownership. Not SAP figures.
DAY_RATE = 900                 # USD per person-day
UPGRADES_PER_YEAR = 1          # upgrade or feature pack events Calder applies per year
YEARS = 5
RUN_DAYS_PER_YEAR = {"Side-by-side (SAP BTP)": 6, "On-stack: key user": 1, FALLBACK: 2}
RETEST_DAYS_BY_LEVEL = {"A": 0.5, "B": 1.5, "C": 4, "D": 8}   # per upgrade, per extension


def decide(req: dict) -> tuple:
    """Walk the tree; return (decision, path of (question, answer))."""
    path = []
    for key, question, outcome, yes_decides in TREE:
        answer = req.get(key, False)
        path.append((question, "yes" if answer else "no"))
        if answer == yes_decides:
            return outcome, path
    return FALLBACK, path


def worst_level(objects: list) -> str:
    """An extension is rated by the lowest level any of its uses reaches."""
    if not objects:
        return "-"
    return max(level_of(o, m) for o, m in objects)   # "D" > "C" > "B" > "A"


def five_year_cost(decision: str, level: str, build_days: float) -> float:
    run = RUN_DAYS_PER_YEAR.get(decision, 2)
    retest = RETEST_DAYS_BY_LEVEL.get(level, 0) * UPGRADES_PER_YEAR
    return (build_days + YEARS * (run + retest)) * DAY_RATE


def register() -> None:
    rows, counts = [], {}
    for r in REQUIREMENTS:
        decision, _ = decide(r)
        level = worst_level(r.get("objects", []))
        counts[decision] = counts.get(decision, 0) + 1
        rows.append((r["id"], r["process"], r["text"], decision, level, r["why"]))
        print(f"{r['id']}  {r['process']:<6} {decision:<33} {level}  {r['text']}")
    print()
    for decision, n in sorted(counts.items(), key=lambda kv: -kv[1]):
        print(f"{n:>2}  {decision}")
    built = sum(n for d, n in counts.items() if d.startswith(("Side", "On-stack")))
    print(f"\n{built} of {len(REQUIREMENTS)} requirements need something built.")
    lines = ["# Calder fit-gap register (fictional)", "",
             "| ID | Process | Requirement | Decision | Level | Rationale |",
             "| --- | --- | --- | --- | --- | --- |"]
    lines += [f"| {' | '.join(row)} |" for row in rows]
    REGISTER.write_text("\n".join(lines) + "\n", encoding="utf-8")
    print(f"Wrote {REGISTER}.")


def explain(rid: str) -> None:
    req = next((r for r in REQUIREMENTS if r["id"] == rid.upper()), None)
    if req is None:
        sys.exit(f"No requirement {rid!r}. Use one of {', '.join(r['id'] for r in REQUIREMENTS)}.")
    decision, path = decide(req)
    print(f"{req['id']}: {req['text']}")
    for question, answer in path:
        print(f"  {answer:<3}  {question}")
    print(f"  ->   {decision}: {req['why']}")
    for label, key in (("As decided", "objects"), ("As requested", "requested")):
        for obj, mode in req.get(key, []):
            state = API_STATES.get(obj, "unknown")
            print(f"  {label}: {mode} {obj} ({state}) -> level {level_of(obj, mode)}")


def cost() -> None:
    print(f"Five-year cost at {DAY_RATE} USD/day, {UPGRADES_PER_YEAR} upgrade event(s) a year:\n")
    for r in REQUIREMENTS:
        decision, _ = decide(r)
        if "objects" in r and decision.startswith(("Side", "On-stack")):
            lvl = worst_level(r["objects"])
            print(f"{r['id']}  as decided   level {lvl}  {five_year_cost(decision, lvl, r['build_days']):>9,.0f} USD")
        if "requested" in r:
            lvl = worst_level(r["requested"])
            print(f"{r['id']}  as requested level {lvl}  {five_year_cost(FALLBACK, lvl, r['requested_days']):>9,.0f} USD")
    print("\nDay rate, effort and upgrade counts are course inputs, not SAP figures.")


def online() -> int:
    """Download SAP's current files and compare every state in API_STATES."""
    current = {}
    for name in CR_FILES:
        try:
            with urllib.request.urlopen(CR_BASE + name, timeout=60) as resp:
                data = json.load(resp)
        except OSError as err:
            sys.exit(f"Could not download {name}: {err}. Check your network or proxy; the offline states still work.")
        items = data.get("objectReleaseInfo") or data.get("objectClassifications") or []
        for item in items:
            key = item.get("objectKey", "").upper()
            if key in API_STATES and item.get("state"):
                current.setdefault(key, item["state"])
    changed = 0
    for obj, state in API_STATES.items():
        now = current.get(obj, "not listed")
        flag = "OK     " if now == state else "CHANGED"
        changed += now != state
        print(f"{flag}  {obj:<30} course: {state:<16} SAP now: {now}")
    print(f"\n{changed} change(s) since 2026-10-28.")
    return 1 if changed else 0


def main() -> None:
    parser = argparse.ArgumentParser(description="Calder fit-gap register.")
    parser.add_argument("--explain", metavar="ID", help="show the decision path for one requirement")
    parser.add_argument("--cost", action="store_true", help="five-year cost of each extension")
    parser.add_argument("--online", action="store_true", help="re-check API states against SAP's files")
    args = parser.parse_args()
    if args.explain:
        explain(args.explain)
    elif args.cost:
        cost()
    elif args.online:
        sys.exit(online())
    else:
        if not Path("unit1b").is_dir():
            sys.exit("Can't find the unit1b folder. Run this from your orchestrate-course folder.")
        register()


if __name__ == "__main__":
    main()

The script's Calder content sits in three blocks at the top. REQUIREMENTS holds the workshop answers for each request, TREE holds the decision order from this topic, and API_STATES holds the state of each SAP object an extension would touch, copied from SAP's Cloudification Repository on 28 October 2026. The day rate, effort days and upgrade count near the bottom of those blocks are course inputs for a fictional company.

Step 3: Build the register

  1. Run the script with no options:

    • Windows (PowerShell):

      python unit1b\fit_gap.py
    • macOS / Linux:

      python unit1b/fit_gap.py
  2. You should see:

R01  O2C    Configuration                     -  Hold orders when a customer is over its credit limit
R02  O2C    On-stack: key user                A  Pump test certificate number on order item and delivery note
R03  O2C    Side-by-side (SAP BTP)            A  Distributors see order status and dates without SAP users
R04  O2C    Process change                    -  Sales director releases credit blocks by phone
R05  P2P    Configuration                     -  Plant manager approves POs above 25,000 USD
R06  P2P    Nothing: decline                  -  Rebuild CP20's custom report ZMM_OPEN_PO_AGING
R07  P2P    Process change                    -  Keep Augsburg's program that writes dates into PO items
R08  P2P    On-stack: developer (ABAP Cloud)  A  Monthly intercompany price for CP-IMP200 at cost plus 8%
R09  Plan   Configuration                     -  Final inspection at goods receipt for ES-300 only
R10  Plan   Standard feature                  -  See TEST1 load against capacity and the maintenance downtime

 3  Configuration
 2  Process change
 1  On-stack: key user
 1  Side-by-side (SAP BTP)
 1  Nothing: decline
 1  On-stack: developer (ABAP Cloud)
 1  Standard feature

3 of 10 requirements need something built.
Wrote unit1b/calder_fit_gap.md.
  1. Open unit1b/calder_fit_gap.md and preview it with Ctrl+Shift+V (Cmd+Shift+V on a Mac). You should see the register as a table with a rationale per line.

Read the summary before the details. Seven of ten requirements are solved without building anything, and the three that are built are all level A. That is what a healthy register looks like; on a real program, a register where most lines are extensions is a sign the workshops didn't challenge the requests.

Step 4: Explain one decision

  1. Ask the script why R07, Augsburg's date-writing program, became a process change:

    • Windows (PowerShell):

      python unit1b\fit_gap.py --explain R07
    • macOS / Linux:

      python unit1b/fit_gap.py --explain R07
  2. You should see:

R07: Keep Augsburg's program that writes dates into PO items
  yes  Is there evidence the business needs it (law, contract, KPI, usage)?
  no   Does standard SAP do it with configuration?
  yes  Can the business adopt the standard process instead?
  ->   Process change: Buyers change dates in standard PO apps; the old program is level D
  As requested: write EKPO (notToBeReleased) -> level D
  1. Now try --explain R08. It walks all six questions to the developer option, then shows the objects: as decided, the job writes through two released objects (level A); as first requested by Augsburg's team, it would update open purchase orders with the classic BAPI_PO_CHANGE (level B).

This is the evidence for a "no". The path shows which question decided, and the object line shows the clean core level of what was asked for, taken from SAP's own files rather than from opinion.

Step 5: Compare the five-year cost

  1. Run the cost view:

    • Windows (PowerShell):

      python unit1b\fit_gap.py --cost
    • macOS / Linux:

      python unit1b/fit_gap.py --cost
  2. You should see:

Five-year cost at 900 USD/day, 1 upgrade event(s) a year:

R02  as decided   level A      9,450 USD
R03  as decided   level A     51,750 USD
R07  as requested level D     48,600 USD
R08  as decided   level A     22,050 USD
R08  as requested level B     24,750 USD

Day rate, effort and upgrade counts are course inputs, not SAP figures.

Read R07 against its decision: as requested, the level D program costs 48,600 USD over five years in this model; the process change costs no build at all. R03, the distributor app, is the most expensive line even at level A, because a side-by-side app has its own running cost. It is still the right call: its users have no SAP access, and the alternative is giving distributors SAP users.

Step 6: Re-check the clean core inputs online (optional)

SAP updates the Cloudification Repository regularly. An object released today stays released, but objects can be deprecated and new successors added. This step checks the course's API_STATES against SAP's current files.

  1. Run:

    • Windows (PowerShell):

      python unit1b\fit_gap.py --online
    • macOS / Linux:

      python unit1b/fit_gap.py --online
  2. After a few seconds you should see (on 28 October 2026):

OK       I_SALESORDER                   course: released         SAP now: released
OK       I_SALESORDERITEM               course: released         SAP now: released
OK       I_PURCHASEORDERAPI01           course: released         SAP now: released
OK       I_PURCHASEORDERITEMAPI01       course: released         SAP now: released
OK       I_PURCHASEORDERTP_2            course: released         SAP now: released
OK       I_PURCHASINGINFORECORDTP       course: released         SAP now: released
OK       I_SLSPRCGCONDITIONRECORDTP_2   course: released         SAP now: released
OK       BAPI_PO_CHANGE                 course: classicAPI       SAP now: classicAPI
OK       EKPO                           course: notToBeReleased  SAP now: notToBeReleased
OK       EKKO                           course: notToBeReleased  SAP now: notToBeReleased

0 change(s) since 2026-10-28.

If a line says CHANGED, SAP's state for that object differs from the course's copy. Look the object up in the repository's CSV files and update API_STATES. The script ends with exit code 1 when anything changed, so a scheduled job could alert on it.

Step 7: Save your work in Git

git add unit1b/fit_gap.py unit1b/calder_fit_gap.md
git commit -m "Add Calder fit-gap register"

What each part of the code does

Part What it does
API_STATES The state of each SAP object an extension touches, from SAP's private cloud and classic API files
level_of() Turns a state and a read or write into a clean core level: released A, classic API B, internal read C, internal write D
TREE, FALLBACK The decision questions in order, and the outcome when every answer is no
REQUIREMENTS Ten Calder requests with workshop answers, objects as decided and as requested, and effort
decide() Walks the tree and returns the decision with the path taken
worst_level() Rates an extension by the lowest level of any object it uses
five_year_cost() Build plus five years of running and per-upgrade retest effort
register(), explain(), cost(), online() The four views: the register file, one decision's path, the cost comparison, the live re-check

If something goes wrong

What you see What it means What to do
python is not recognized, or command not found The terminal can't find Python Repeat Step 1 of the course setup; on macOS and Linux use python3 until .venv is active
No module named ... You pasted only part of the script The script uses only built-in modules; paste it again in full
Can't find the unit1b folder You're not in the course folder Open orchestrate-course in VS Code and run from its terminal
No requirement 'R11' The ID isn't in REQUIREMENTS Use an ID from the list the message prints, or add your requirement first
Could not download ... with --online No network, or a company proxy blocks GitHub Try another network, or set the HTTPS_PROXY environment variable your IT team gives you; Steps 3 to 5 work offline
CERTIFICATE_VERIFY_FAILED with --online A company proxy inspects HTTPS traffic Ask IT for the proxy's certificate setup for Python, or skip Step 6
Your output differs from this page You changed REQUIREMENTS or the cost inputs Fine if you meant to; copy the script again to compare

This script needs no API key, so key problems don't apply here.

The SAP way

On a real program, each step of this topic has an SAP tool. As of October 2026:

Job SAP tool Edition Note
Show the standard process SAP Signavio Process Navigator; starter system Public Edition workshops as described; private edition uses the same content Process flows, test scripts and tutorials (SAP Learning)
Record and track requirements SAP Cloud ALM requirements, user stories, features All Approval, refinement, transports and test cases (SAP Learning)
Configure Customizing (IMG); SAP Central Business Configuration in Public Edition All Example: flexible workflow for purchase orders, configured in the app Manage Workflows for Purchase Orders with conditions such as total net amount (SAP Learning)
Find released APIs SAP Business Accelerator Hub; Cloudification Repository All S/4HANA The hub lists APIs, events and CDS views per edition (SAP Learning)
Find patterns and samples SAP Extensibility Explorer All Sample scenarios and use cases (same source)
Rate custom code ABAP test cockpit with the Cloudification Repository checks Private edition, on-premise; ECC for analysis "Usage of APIs" and "Allowed Enhancement Technologies" (SAP on GitHub)
Watch clean core over time RISE with SAP Methodology dashboard RISE customers Insight into clean core adoption (SAP News)
Retire unused code SCMON or UPL, SUSG, Custom Code Migration app ECC and S/4HANA conversions Collect 6 to 18 months, including a year-end

Licensing notes. Key user apps and ABAP Cloud are part of S/4HANA. Side-by-side apps need SAP BTP services; SAP's private edition training notes that SAP Business Application Studio needs an additional license, while it lists SAP Build as included with the GROW with SAP package (SAP Learning). Check your own contract.

The three standard answers in Calder's register come from SAP features verified for this topic: blocked orders go to a credit analyst who releases them in Manage Documented Credit Decisions (SAP Learning); an inspection type in a material's master data creates an inspection lot at goods receipt from production for that material only (SAP Learning); and capacity evaluation with Manage Work Center Capacity plus leveling in the Capacity Scheduling Board covers the test bench request (SAP Learning).

Build vs. SAP

Need Build your own Use SAP Choose
Hold the register A script and markdown file, as here SAP Cloud ALM requirements Cloud ALM on a real program: it links requirements to transports and tests. A script is fine for a workshop or a proposal
Rate extensions A lookup like API_STATES ATC with the Cloudification Repository checks ATC; it reads the code, not a list someone typed
Find unused code Asking the business SCMON, UPL, Custom Code Migration app SAP; memories of usage are unreliable
Decide The tree in this topic SAP's guidance, the standardization board Both: SAP gives the rules; the board applies them to your business

Architecture view

Where it sits. The register sits between fit-to-standard and the build. Each decision lands in one place in Calder's landscape:

flowchart LR
  subgraph S4[CP10 S/4HANA private edition]
    CFG[Configuration<br/>R01 R05 R09]
    STD[Standard feature<br/>R10]
    KU[Key user<br/>R02]
    DEV[ABAP Cloud<br/>R08]
  end
  subgraph BTP[SAP BTP]
    APP[Distributor app<br/>R03]
  end
  APP -->|released APIs| S4
  D[Distributors] --> APP
  X[Declined or process change<br/>R04 R06 R07] -.-> REG[Register and ADRs]

Non-functional requirements that decide the design

Requirement Typical target If missed
Upgrade stability New extensions at level A; nothing below B without board approval Upgrades slip or break production processes
Retest effort Every extension has an automated or scripted test in Cloud ALM Each upgrade becomes a manual project
Security Side-by-side apps use the end user's identity or a narrowly scoped technical user Outsiders see data beyond their own orders
Cost Five-year cost recorded for each extension Running costs surprise the budget after go-live
Traceability Each register line links to evidence and a decision owner Old debates reopen; audits can't see why

Integration and identity. On-stack extensions run inside S/4HANA under the user's own roles. The distributor app (R03) reads sales orders through released APIs; it should pass the distributor's identity, or, when a technical user is used, filter strictly by the customer number taken from the signed-in distributor's identity (never from a request parameter the caller can change), so Hallam sees only Hallam's orders. Role design for this is covered in Roles and authorization design.

The decision to record (in the ADR form from Writing a solution design doc):

  • Context: Augsburg's buyers ask to keep their ECC program that writes changed delivery dates straight into purchase order items (R07) after CP20 moves into CP10's system. The program writes to EKPO, which SAP lists as not to be released.
  • Options: (1) rebuild the program as it is; (2) rebuild it as an ABAP Cloud job through the released purchase order object I_PurchaseOrderTP_2; (3) adopt the standard: buyers change dates in the standard purchase order apps.
  • Decision: (3). The need behind the program is real, but standard apps meet it, and the requested design is level D.
  • Consequences: Augsburg's buyers need training on the standard apps before cutover. If the volume of date changes proves too high for manual work, option (2) is the fallback, at level A. The old program is not migrated.

What a design review will ask

  1. What share of requirements became extensions, and what evidence backed the ones declined?
  2. Which extensions are below level A, and who approved them?
  3. Who runs and pays for each side-by-side app after go-live?
  4. How is each extension retested at upgrade, and how long does that take?
  5. Which Augsburg programs were retired on usage data, and was a year-end inside the window?

Production concerns

  • Security and SAP authorizations. Key user apps and ABAP Cloud work under SAP roles; restrict who may create custom fields and logic in development, and transport them like any change. Side-by-side apps need their own identity and authorization design; never give a portal a broad technical user (Agent permissions and SAP authorizations).
  • Evaluation. Treat the register as a living artifact: count extensions by level each quarter, and track how many standard features replaced an extension after an upgrade.
  • Cost. Record the five-year cost of each extension at decision time. Re-check it at each upgrade with the actual retest effort.
  • Operations. Every extension needs an owner, a test and a retirement trigger ("delete when SAP's standard covers X").
  • Clean core. Run ATC with the Cloudification Repository checks in the development system, and let a failed check block the transport of new level C or D code.

Pitfalls

  • Treating the workshop as requirements gathering. Fit-to-standard demonstrates first; the business reacts to the standard, not to a blank page.
  • Rebuilding the old system. "Same as ECC" requests need usage data, not sympathy.
  • Choosing side-by-side for everything. It is upgrade-stable, but it adds an app to run, secure and pay for.
  • Rating by intent, not by code. An app that is "mostly level A" with one table write is level D.
  • Deciding without the cost tail. Build estimates without upgrade retest effort understate every extension.
  • Never revisiting. Each upgrade may bring a standard feature that makes an extension removable; nobody checks unless someone owns it.

Exercise

Extend Calder's register to two more processes and write the "no" memo for one requirement.

  1. Run python unit1b/fit_gap.py and confirm the summary says 3 of 10 requirements need something built.
  2. Open unit1b/fit_gap.py. Add R11 for maintain-to-settle: "Technicians record crane inspection results on a tablet in the plant, offline". Answer the tree questions honestly (evidence yes; configuration no; adopt standard no; standard feature: say no unless you find one in SAP's documentation; outside users or own lifecycle: decide and note why). Give it objects of your choice from API_STATES and build_days.
  3. Add R12 for warehouse execution: "Show the Rockford dock team which trucks are late, from data already in S/4HANA". Decide whether it is a key user query or something larger, and set the answers to match.
  4. Run the script again and --explain R11 and --explain R12. Check the paths match your reasoning.
  5. Run --cost and note whether your two lines appear.
  6. Open unit1b/calder_landscape_decision.md from Landscape and deployment (create the file if you skipped that exercise). Under a new heading Fit-gap decisions, paste the register table from unit1b/calder_fit_gap.md.
  7. Below it, under The no for R07, write five lines for the Augsburg buyers' manager: the need you accept, the standard alternative, the clean core level of the requested design, the five-year cost comparison labeled as synthetic, and what happens if volumes prove too high.
  8. Commit both files with Git.

Done when: the register shows 12 requirements with a decision and rationale each, --explain paths for R11 and R12 match what you wrote, and the R07 memo has all five lines, with the cost labeled synthetic. Keep the file: the roles topic designs access for the extensions in this register.

Check yourself

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

1In SAP Activate, what has to happen to a delta requirement from a fit-to-standard workshop before it enters the backlog for the Realize phase?

Answer: A. SAP's Explore guidance has the product owner validate deltas and configuration needs before they join the product backlog, which is then prioritized. ATC rates code, which doesn't exist yet at this point.

2An extension reads released CDS views, calls one classic BAPI and writes one field directly into EKPO. What is its clean core level?

Answer: B. An extension takes the level of the lowest technique it uses. Writing into an SAP table is level D, whatever the rest of the code does.

3In the script, why does R08 end at the developer option rather than key user?

Answer: D. The tree asks its questions in order, and the developer option is the fallback when all answers are no. Key user apps are available in all S/4HANA deployment options, so availability isn't the reason.

4Calder's distributor app reads sales orders from S/4HANA with a technical user. What must the design add?

Answer: B. A technical user sees everything its roles allow, so the app must limit each distributor to its own orders, or pass the distributor's identity. The other choices add cost or risk without closing the exposure.

5Augsburg insists its ECC report must be rebuilt. What evidence settles the question, by SAP's guidance?

Answer: A. SAP recommends usage data from production, over a window that includes a year-end close, aggregated with SUSG. Memories of usage are unreliable, and size or speed says nothing about need.

6A requirement is real, standard SAP doesn't cover it, and the users are S/4HANA buyers who need logic that changes core data in the same transaction. What do you choose?

Answer: C. Same-transaction logic for S/4HANA users points on-stack, and level A keeps it upgrade-stable. Side-by-side fits outsiders or loose coupling; classic code needs board approval; a process change only works when the business can adopt the standard.

7Why does the five-year cost of the requested R07 design exceed its build cost by so much?

Answer: D. In the course model, cost is build plus years times running and per-upgrade retest effort, and retest effort grows with the level. The model has no license fee per object and a flat day rate.

8What happens to an extension's place in the register after a later upgrade adds the same function to standard SAP?

Answer: B. SAP suggests recording why each deviation was accepted so it can be removed when the standard catches up. A retirement trigger per extension makes that review happen.

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