A system can do everything the business asked for and still fail on the first month-end. The process works on 20 test orders. It doesn't work on 4,000 invoices in one night.
Functional requirements say what the system must do: block an order over its credit limit, match an invoice to a receipt. Non-functional requirements say how much, how fast, how reliably and how traceably it must do it. How many billing documents on the busiest night? How long may the system be down? How much data can be lost in a disaster? How long are invoices kept, and who can prove who changed a supplier's bank account?
Nobody volunteers these numbers. Someone has to collect them, size the system and the night's job schedule against the peak, not the average, and write down who delivers each one. In a cloud edition, part of the answer is SAP's and is fixed by contract. The rest is yours.
This topic uses Calder Pumpworks, the fictional pump maker of this unit. Its US company CP10 runs on SAP S/4HANA Cloud Private Edition under RISE; its German company CP20 moves into the same system in July 2027.
Peaks break systems, averages don't. Calder bills about 286 documents on an average working day, but 2,400 on the last working day of the month. A design sized for the average day is 8.4 times too small on the night that matters most.
A late night job is a business problem. If billing output finishes late, invoices reach customers a day later and cash arrives a day later. If the planning run isn't done when planners arrive, they work from yesterday's shortages. Order-to-cash, plan-to-produce and record-to-report all depend on the month-end night.
Joining time zones shrinks the window. Today Calder's heavy jobs can run from 22:00 until Rockford's shift starts at 06:00. After Augsburg joins the same system, Augsburg's working day starts at about midnight Rockford time. The quiet window drops from eight hours to two.
Contracts cover less than people assume. SAP's availability promise is a monthly percentage with credits, not a guarantee for your month-end night. Its long-distance disaster recovery service can lose up to 30 minutes of data and take up to 12 hours to restore service. Whether that is acceptable is a business decision that needs a number.
Audit and retention are legal obligations. Who changed a supplier's bank details, who used an emergency ID, how long invoices must be kept and when personal data must be deleted: auditors ask, and the answers must come from the system.
As of October 2026, SAP publishes the contract terms for its cloud editions on its agreements pages. The values below come from the versions opened for this topic; your order form can change them, and newer versions may apply.
Daily full and log backups kept 30 days (SLA); disaster recovery with RPO 30 minutes (long distance) or 0 (short distance, selected regions) and RTO 12 hours, or 4 hours as an option (DR service description)
A task list splits SAP's standard services, optional services and the customer's tasks (roles and responsibilities)
Weekly full, nightly incremental and frequent log backups; enhanced disaster recovery is a separate paid service (same supplement)
SAP runs the system; the customer schedules and monitors its own business jobs (SAP Learning)
ECC or S/4HANA on-premise
Whatever your own operations deliver
Yours to design, run and test
Everything is yours or your hosting partner's
For sizing, SAP provides Quick Sizer, which estimates CPU, memory, disk and I/O from expected throughput or user counts, and a sizing report for ECC systems moving to S/4HANA (SAP Note 1872170) (SAP sizing). In a cloud edition, SAP provides the capacity; you provide the volumes, and you own the growth forecast.
A good checklist names a number, a measure, an owner and who delivers it. Calder's has 15 lines; these are the areas.
Area
Calder's requirement (workshop inputs)
Delivered by
Volumes and peaks
2,400 billing documents on the last working day, 4,000 after the rollout; 4,200 materials in the planning run, 7,100 with Augsburg
Calder supplies the numbers
Batch window
Heavy month-end jobs end before the next site's working day: 06:00 today, midnight Rockford time after the rollout
Calder schedules and monitors
Response time
95% of sales order saves under 2 seconds during the month-end run
Calder measures; SAP provides capacity
Interfaces
The reporting data layer is fresh by 05:00; failed EDI messages raise an alert within 15 minutes
Calder
Availability
99.7% a month for production (contract)
SAP
Disaster recovery
RPO 30 minutes and RTO 12 hours contracted; the business tolerates 4 hours at month end
SAP runs it; Calder decides whether to buy the 4-hour option
Retention and archiving
Billing documents stay 24 months in the database, then archived; retention periods set by legal
Calder designs; legal decides
Audit
Supplier bank changes reviewed weekly; emergency ID sessions reviewed within 2 working days
Calder
AI workloads
The invoice agent stays under 70% of its model rate limit at peak
Calder
Two lines in that table caught Calder out. The 4-hour gap between the contracted 12 hours and what the business tolerates has to be decided, not discovered in a disaster. And interface monitoring was first marked "SAP", but SAP's standard system monitoring excludes interfaces and jobs (roles and responsibilities).
"The cloud provider handles performance." SAP provides capacity and runs the technical platform. Expensive SQL in your own code, job design and interface volumes are yours, per SAP's task list.
"99.7% means we're never down at month end." It is a monthly measure with excluded downtime and service credits. It says nothing about which hours are affected.
"Disaster recovery means no data loss." SAP's long-distance disaster recovery allows up to 30 minutes of lost data. Somebody must know how to find and re-create what was lost.
"Sizing is the hosting team's job." Sizing needs business volumes and peaks. Only the business knows them.
"We'll archive later." Without a retention and archiving design, data only grows, and later becomes a project nobody funds.
"AI calls are just another interface." They add latency per step, a cost per call and a rate limit that can stop a month-end batch.
Pick one answer for each question. The explanation appears after you choose.
1Calder's system handles an average working day easily. Why is that not enough?
Answer: D. Calder bills about 286 documents on an average day and 2,400 on the last working day, 8.4 times more. A design sized for the average fails on the night that matters most.
2After Augsburg joins Calder's S/4HANA system, why does the batch window shrink?
Answer: C. Augsburg is about seven hours ahead. Heavy jobs that once had from 22:00 to 06:00 now have about two hours before Augsburg's users start work in the same system.
3Calder's private edition contract promises 99.7% availability. What does that tell the business about month end?
Answer: C. The SLA is measured over the month, with scheduled and other downtime excluded, and its remedy is service credits. It says nothing about which hours are covered, so month-end needs its own plan.
4SAP's long-distance disaster recovery has an RPO of 30 minutes. What must Calder prepare?
Answer: D. Up to 30 minutes of data may be lost. Somebody on Calder's side must know how to find what was lost and re-create it, and that has to be planned before a disaster.
5Interface monitoring was marked "delivered by SAP". What is wrong with that?
Answer: C. SAP's roles and responsibilities list says its system monitoring doesn't cover interfaces, connections or jobs. Unless Calder buys that as an extra service, it is Calder's job, with a named owner.
6Who should supply the volume numbers for sizing?
Answer: C. Sizing translates business requirements into hardware requirements. Only the business knows how many orders, invoices and materials it has at peak and how they will grow.
Ask a question
Testing: only staff see this
Stuck on something in this layer? Ask it here. Questions are answered in the order they arrive, and the answer appears under My questions.
Sign in (free) to ask a question. You can ask anonymously.
A non-functional requirement is a number, a measure, an owner and a party who delivers it. "Fast", "always available" and "fully audited" are wishes. "95% of sales order saves under 2 seconds during the month-end run, measured at the save step, owned by Calder operations" is a requirement.
Three rules make the numbers useful:
Size for the peak. The peak hour of the peak day of the peak year after the next rollout.
Every requirement crosses a contract line. In a cloud edition, some parts are SAP's by contract and the rest are yours. Write down which is which, from SAP's own documents, before go-live.
Check the requirements in code. A checklist in a spreadsheet goes stale. A small script that recomputes the month-end night every time a volume changes catches the problem in design, not in production.
Documents per day, peak day, peak hour, growth, by document type
What is the busiest hour, and what makes it busy?
Batch windows
Jobs, their order, their duration at peak, the deadline for each
Which users start when, in which time zone?
Response time
Target per critical step, at peak
Which step must stay fast while the batch runs?
Interfaces
Volumes, latency, failure handling, monitoring
Who notices when a message fails at 02:00?
Availability and recovery
Uptime, backups, RPO, RTO, DR tests
How long can each process stop at month end?
Retention and archiving
Residence time, retention period, deletion
Which law sets the period, in which country?
Audit
What is logged, who reviews it, how often
What will the auditor ask for first?
AI workloads
Calls per business document, latency, cost, rate limits
What happens when the model provider throttles?
Collect them in workshops with the process owners, the same people who gave you the functional requirements in Fit-to-standard and extension choices. Ask for peaks, not averages, and ask what happens on the day after the peak.
SAP describes sizing as an iterative process that turns business requirements into hardware requirements. It distinguishes user-based sizing (count active users, quick but rough) from throughput-based sizing (count business documents, more reliable) and from customer performance tests (SAP sizing). Quick Sizer implements the first two. For an ECC system moving to S/4HANA, SAP points to a sizing report described in SAP Note 1872170 (same page).
Throughput is the input that only the business can give. Calder's workshop values:
Object
Average day (CP10)
Last working day
Peak factor
Last working day after rollout
Sales order items
429
720
1.7
1,160
Billing documents
286
2,400
8.4
4,000
Supplier invoices
71
600
8.4
1,000
Goods movements
1,429
1,800
1.3
3,000
Materials in the MRP run
4,200
4,200
1.0
7,100
Billing and invoices are peaky; order entry and goods movements are not. That difference decides where the design effort goes. In a cloud edition, SAP sizes and scales the infrastructure. In the private edition task list, SAP provides capacity reports and a periodic capacity plan, while determining whether existing hardware can meet growth is the customer's task unless bought as an extra service (roles and responsibilities).
Month-end work runs as background jobs. In S/4HANA Cloud, SAP separates technical jobs, which SAP sets up and runs, from business (application) jobs, which the customer schedules, controls and monitors. Two details from SAP's training matter for design: a job that shows Finished isn't necessarily successful, so someone must read the job log; and each job runs under a user, so when that person leaves, the job needs a new user (SAP Learning). Calder's leaver 00100094 from Hire-to-retire touchpoints would break any job scheduled under that user.
Two of Calder's heaviest jobs have documented ways to go faster:
Billing. The billing due list can be processed collectively in the background, and the system can split it into several background jobs that run at the same time. SAP also recommends leaving the detailed log off unless needed (SAP Learning).
MRP.MRP Live (transaction MD01N, or the Schedule MRP Runs app) runs the planning in one database procedure on SAP HANA. It doesn't support every process, so the system plans some materials with classic MRP automatically (SAP Learning). Splitting the run by plant gives two shorter jobs.
The window itself is set by users, not by the system. This is Calder's month-end night after the rollout, as the script computes it:
flowchart LR
T[22:00 last truck] --> B[BILL 100 min]
B --> O[OUT 133 min]
O --> E[EDI 20 min]
B --> X[EXTR 50 min]
M[22:30 MRP 107 min]
O -. ends 01:54 .-> A[00:00 Augsburg<br/>starts work]
M -. ends 00:17 .-> A
A background job and a user saving an order share the same system. During the overlap, the target that matters is the dialog one: Calder's is 95% of sales order saves under 2 seconds. Measure it in a load test with the batch running, not on an empty system. In the private edition task list, monitoring critical business transactions is the customer's task unless bought from SAP (roles and responsibilities).
The private edition SLA measures availability per system per calendar month, 24 hours a day, minus downtime. Scheduled, agreed and emergency downtime are excluded, and the remedy for a miss is a service credit of 2% of the monthly fee per percentage point below target; missing the production target three months in a row also gives the customer a right to terminate the order form (SLA). For a 31-day month at 99.7%, that allows 133.9 minutes of unplanned downtime.
Disaster recovery is a separate service. Long-distance recovery replicates asynchronously, with an RPO of 30 minutes. Short-distance recovery replicates synchronously with an RPO of 0, where the region allows. Both have an RTO of 12 hours, or 4 hours with the 4-hour option. SAP runs one DR failover test a year, and each DR implementation also needs the customer's own testing (DR service description).
Turn those numbers into business exposure. If a disaster hits in the middle of Calder's billing run, 30 minutes of lost data is up to 1,200 billing documents today. Some of them may already be on their way to customers by EDI. Validating the logical integrity of restored data is listed as the customer's task (roles and responsibilities).
Two different numbers apply to every document type. Residence time is how long it stays in the database for daily work. Retention period is how long it must exist at all, set by law, tax rules or policy in each country. Calder's legal team sets the retention periods; this course doesn't state any.
SAP Information Lifecycle Management (ILM) manages data by policies and rules: where data lives, how long it is kept, and when it can be destroyed, and it can also retire legacy systems (SAP ILM). For personal data, S/4HANA can block data after its business purpose ends, so that only specially authorized users can see it, and destroy it when retention expires (SAP Community).
In the private edition task list, monitoring table growth is SAP's task, while developing alternative plans such as archiving is the customer's unless bought (roles and responsibilities).
SM20 or RSAU_READ_LOG; in S/4HANA Cloud Public Edition a Security Audit Log app and API (SAP, 2025)
Read Access Logging
Who read which personal data, when
A separate component for monitoring read access (SAP Community)
Logging isn't reviewing. In the private edition task list, SAP provides audit log information on request for incident investigations, while customer-specific Security Audit Log analysis is the customer's unless bought (roles and responsibilities). Calder's unreviewed emergency session FF-0008, a bank key fix from Roles and authorization design, is exactly what a weekly review should catch.
Most performance failures in a clean system come from code and interfaces you add. The usual suspects, as course guidance:
Data to code instead of code to data. Reading large tables into the application and filtering there. SAP's guidance for ABAP on HANA is to push work into the database through ABAP SQL and CDS views and return only results (SAP Community).
A database call per record. A loop that reads one order at a time works on 20 test orders and fails on 4,000.
One API call per document. An interface that calls the sales order API for every order each night, instead of reading changes in pages or by delta.
Synchronous calls inside a save. A check that calls an external system while the user waits. When that system slows down, every save slows down.
Testing on small data. A custom report that is fast in development, with 50 documents, and slow in production, with 5 million.
Optimizing expensive SQL is the customer's task in the private edition task list unless bought from SAP (roles and responsibilities). Integration patterns get their own unit, Unit 5B, starting with how SAP systems talk.
An AI feature adds three non-functional numbers to every business document it touches:
Latency per step. A model call takes seconds, not milliseconds. Three calls per invoice at a p95 of 2.5 seconds add up to 7.5 seconds in the worst case. That is fine for an overnight agent and unusable inside a save. See Latency budgets and performance.
Cost per call. Volume times calls times price, at peak and per month. See Token economics.
Rate limits. Model endpoints limit requests per minute. A month-end peak that exceeds the limit turns into throttled requests, retries and a late batch. Keep peak use well under the limit; Running AI on BTP uses 70%, and so does this topic.
Calder's invoice triage agent (AGT_INV_TRIAGE, from the roles topic) uses three calls per invoice. With the agent redesigned as a multi-step agent using 12 calls, the after-rollout peak would use 83% of the course's limit, above the 70% headroom.
You will write one script that keeps Calder's non-functional requirements honest. It lists the 15 requirements, compares average and peak volumes, simulates the month-end night today and after Augsburg joins, turns the contract's availability and recovery numbers into business exposure, checks the invoice agent against its rate limit, and fails when a requirement is incomplete, assigned to the wrong party or missed.
flowchart LR
P[calder_pack/<br/>company codes,<br/>plants] --> S[nfr.py]
R[Requirements, volumes,<br/>jobs in the script] --> S
S --> K[Peaks]
S --> W[Month-end night]
S --> V[Availability]
S --> A[AI load]
S --> C[Check]
S --> F[calder_nfr.md]
The script connects to no SAP system. It models the design decisions with numbers small enough to read.
In VS Code, create a new file, paste the script below, and save it as nfr.py inside the unit1b folder.
"""Collect, size and check Calder Pumpworks' non-functional requirements for the
S/4HANA system of CP10, today and after CP20 (Augsburg) joins it in July 2027.
Calder Pumpworks is a fictional company. Every volume, rate, duration, target and
rule below is a course value or workshop input, not an SAP standard value, an SAP
benchmark or a real project figure. The SLA, backup and disaster recovery values
come from the SAP agreement documents named in the topic, which a real contract's
order form can change. The script reads the Calder sample pack (unit1b/calder_pack/)
and needs no account, no key and no network.
Run it from your course folder:
python unit1b/nfr.py the requirements checklist
python unit1b/nfr.py --peaks average day vs month-end day, today and after the rollout
python unit1b/nfr.py --window the month-end night, today
python unit1b/nfr.py --window --after the month-end night after the rollout
python unit1b/nfr.py --availability downtime, data loss and recovery exposure
python unit1b/nfr.py --ai the invoice agent's load against its rate limit
python unit1b/nfr.py --ai --calls 12 the same with 12 model calls per invoice
python unit1b/nfr.py --check every requirement complete, owned and met
python unit1b/nfr.py --write write unit1b/calder_nfr.md
"""
import argparse
import json
import math
import sys
from pathlib import Path
PACK = Path("unit1b") / "calder_pack"
OUT = Path("unit1b") / "calder_nfr.md"
WORKDAYS = 21 # working days in a month (course value)
MONTH_MINUTES = 31 * 24 * 60 # October 2026 has 31 days
# ------------------------------------------------------------- volumes (course values)
# monthly: documents per month. month_end: share of the month created on the last
# working day (WD-1, Friday 2026-10-30). CP20's volumes join CP10's system in July 2027.
VOLUMES = {
"Sales order items": {"CP10": 9000, "CP20": 5500, "month_end": 0.08},
"Billing documents": {"CP10": 6000, "CP20": 4000, "month_end": 0.40},
"Supplier invoices": {"CP10": 1500, "CP20": 1000, "month_end": 0.40},
"Goods movements": {"CP10": 30000, "CP20": 20000, "month_end": 0.06},
}
MRP_SCOPE = {"CP11": 4200, "CP21": 2900} # materials in the planning run, by plant
# ------------------------------------------------- the month-end night (course values)
# Times are minutes after 20:00 Rockford time. sec: seconds per unit, measured in the
# quality system with production-sized data. split: parallel jobs over separate parts
# of the work. heavy: competes with dialog users, so it must end before the next
# site's working day starts.
START = 20 * 60
ROCKFORD_DAY = 6 * 60 + 24 * 60 - START # 06:00 next morning
AUGSBURG_DAY = 24 * 60 - START # 07:00 in Augsburg is 00:00 in Rockford
DATA_LAYER_DUE = 5 * 60 + 24 * 60 - START # 05:00: dashboards fresh for the morning
JOBS = [
{"id": "BILL", "name": "Billing due list (collective billing)", "earliest": 22 * 60 - START,
"after": [], "units": "Billing documents", "sec": 1.5, "split": 1, "heavy": True},
{"id": "OUT", "name": "Billing output (PDF, email, EDI messages)", "earliest": 0,
"after": ["BILL"], "units": "Billing documents", "sec": 2.0, "split": 1, "heavy": True},
{"id": "EDI", "name": "EDI send to distributors", "earliest": 0,
"after": ["OUT"], "fixed": 20, "heavy": False},
{"id": "EXTR", "name": "Delta extraction to the data layer", "earliest": 0,
"after": ["BILL"], "fixed": 35, "fixed_after": 50, "heavy": False, "due": DATA_LAYER_DUE},
{"id": "MRP", "name": "MRP Live, all plants in scope", "earliest": 22 * 60 + 30 - START,
"after": [], "units": "MRP", "sec": 0.9, "split": 1, "heavy": True}, # split 2 = one job per plant
]
# ------------------------------------------- availability and recovery (SAP documents)
SLA_PRD = 99.7 # RISE with SAP S/4HANA Cloud, private edition supplement (v9 2022)
SLA_NONPRD = 95.0 # SLA for private cloud edition services (v7 2023)
RPO_MIN = 30 # long-distance DR, asynchronous replication (DR service description v7 2023)
RTO_H = 12 # standard; a 4-hour option exists
RTO_OPTION_H = 4
TOLERATED_OUTAGE_H = 4 # Calder: longest outage the business accepts at month end (workshop input)
# --------------------------------------------- AI workload (course values, Unit 10 method)
AI = {"calls_per_invoice": 3, "rate_limit_rpm": 60, "headroom": 0.70,
"peak_share": 0.50, "peak_hours": 2, "cost_per_call": 0.004, "p95_call_s": 2.5}
# ------------------------------------------------ who delivers what (SAP R&R document)
# From "RISE with SAP S/4HANA Cloud, private edition and SAP ERP, PCE: roles and
# responsibilities" (v7 2023). "Calder*" means Calder's unless bought from SAP as an
# additional service.
RESP = {
"volume_forecast": ("Calder*", "INFRA_1.5.05_AE: determine if existing hardware can meet growth"),
"job_monitoring": ("Calder", "BASIC_1.8.04: SAP's system monitoring excludes jobs"),
"interface_monitoring": ("Calder", "BASIC_1.8.04: SAP's system monitoring excludes interfaces"),
"response_time": ("Calder*", "BASIC_1.7.07: monitor critical business transactions"),
"interface_latency": ("Calder*", "BASIC_1.8.05: integrate with other systems"),
"availability": ("SAP", "SLA and supplement: system availability of production"),
"backup": ("SAP", "INFRA_1.8.01: standard file system and database backups"),
"restore_validation": ("Calder", "INFRA_1.8.06: validate restored information"),
"dr": ("SAP", "DR service description: replication, failover, one test a year"),
"sql_optimization": ("Calder*", "HANA_1.1.15: analyze and optimize expensive SQL"),
"archiving_design": ("Calder*", "INFRA_1.5.06_AE: alternative plans such as archiving"),
"change_docs": ("Calder", "Supplement 3.3: the customer defines its business processes"),
"sal_analysis": ("Calder*", "BASIC_1.2.19: customer-specific Security Audit Log analysis"),
"ai_quota": ("Calder", "Calder's own application on SAP BTP, outside the S/4HANA service"),
}
# ------------------------------------------------- the requirements (workshop inputs)
NFRS = [
{"id": "N01", "area": "Volumes", "req": "Billing documents on the last working day",
"target": "2,400 today; 4,000 after the rollout", "measure": "Count in the billing job log",
"owner": "Order-to-cash lead", "by": "Calder", "task": "volume_forecast"},
{"id": "N02", "area": "Volumes", "req": "Materials in the MRP run",
"target": "4,200 (CP11) today; 7,100 with CP21", "measure": "MRP run log",
"owner": "Planning lead", "by": "Calder", "task": "volume_forecast"},
{"id": "N03", "area": "Batch", "req": "Heavy month-end jobs end before the next site's day",
"target": "06:00 today; 00:00 Rockford time after the rollout", "measure": "Job end times",
"owner": "Calder operations", "by": "Calder", "task": "job_monitoring"},
{"id": "N04", "area": "Response time", "req": "Sales order save during the month-end run",
"target": "95% under 2 s", "measure": "Response times of the save step at peak",
"owner": "Calder operations", "by": "Calder", "task": "response_time"},
{"id": "N05", "area": "Interfaces", "req": "Data layer fresh for the morning",
"target": "Extraction ends by 05:00", "measure": "Load end time and row counts",
"owner": "Data platform lead", "by": "Calder", "task": "interface_latency"},
{"id": "N06", "area": "Availability", "req": "Production system availability",
"target": "99.7% a month (contract)", "measure": "SAP's monthly SLA report",
"owner": "IT service owner", "by": "SAP", "task": "availability"},
{"id": "N07", "area": "Recovery", "req": "Database backups of production",
"target": "Daily full and log backups, 30 days", "measure": "SAP's service reports",
"owner": "IT service owner", "by": "SAP", "task": "backup"},
{"id": "N08", "area": "Recovery", "req": "Business check after any restore",
"target": "Last 30 minutes of documents checked within 2 h", "measure": "Restore checklist signed",
"owner": "Process owners", "by": "Calder", "task": "restore_validation"},
{"id": "N09", "area": "Recovery", "req": "Disaster recovery",
"target": f"RPO {RPO_MIN} min, RTO {RTO_H} h (contracted)", "measure": "Annual DR test report",
"owner": "IT service owner", "by": "SAP", "task": "dr"},
{"id": "N10", "area": "Operations", "req": "Alert on failed interfaces",
"target": "Alert within 15 min of a failed EDI message", "measure": "Alert log",
"owner": "Integration lead", "by": "SAP", "task": "interface_monitoring"},
{"id": "N11", "area": "Custom code", "req": "No expensive SQL in custom code",
"target": "Every custom report tested on production-sized data", "measure": "Test record per transport",
"owner": "Development lead", "by": "Calder", "task": "sql_optimization"},
{"id": "N12", "area": "Retention", "req": "Billing documents kept, then archived",
"target": "24 months in the database; retention set by legal", "measure": "Archiving runs and ILM rules",
"owner": "Finance and legal", "by": "Calder", "task": "archiving_design"},
{"id": "N13", "area": "Audit", "req": "Changes to supplier bank data logged and reviewed",
"target": "Reviewed weekly", "measure": "Change document report, signed",
"owner": "AP supervisor", "by": "Calder", "task": "change_docs"},
{"id": "N14", "area": "Audit", "req": "Emergency ID sessions reviewed",
"target": "Within 2 working days", "measure": "Security Audit Log review record",
"owner": "Security lead", "by": "Calder", "task": "sal_analysis"},
{"id": "N15", "area": "AI workload", "req": "Invoice agent inside its rate limit at peak",
"target": "Peak under 70% of the limit", "measure": "Requests per minute at peak",
"owner": "AI app team", "by": "Calder", "task": "ai_quota"},
]
def load_pack() -> None:
try:
org = json.loads((PACK / "org_units.json").read_text(encoding="utf-8"))
except FileNotFoundError:
sys.exit(f"Cannot read {PACK}. Run: python unit1b/make_calder_pack.py (Unit 1B setup).")
systems = {c["CompanyCode"]: c["System"] for c in org["CompanyCodes"]}
plants = {p["Plant"] for p in org["Plants"]}
if systems != {"CP10": "S/4HANA", "CP20": "ECC"} or plants != set(MRP_SCOPE):
sys.exit("The pack must hold CP10 on S/4HANA, CP20 on ECC and plants CP11 and CP21. "
"Run make_calder_pack.py again.")
def month_end(obj: str, after: bool) -> int:
v = VOLUMES[obj]
monthly = v["CP10"] + (v["CP20"] if after else 0)
return round(monthly * v["month_end"])
def units(job: dict, after: bool) -> int:
if job.get("units") == "MRP":
return MRP_SCOPE["CP11"] + (MRP_SCOPE["CP21"] if after else 0)
return month_end(job["units"], after)
def clock(minutes: float) -> str:
total = (START + math.ceil(minutes)) % (24 * 60)
return f"{total // 60:02d}:{total % 60:02d}"
def schedule(after: bool) -> list:
done, rows = {}, []
for job in JOBS:
start = max([job["earliest"]] + [done[d] for d in job["after"]])
if "fixed" in job:
minutes = job["fixed_after"] if after and "fixed_after" in job else job["fixed"]
n = None
else:
n = units(job, after)
if job.get("units") == "MRP" and job["split"] > 1:
# one job per plant: the run takes as long as the largest plant in scope
plants = ["CP11", "CP21"] if after else ["CP11"]
minutes = max(MRP_SCOPE[pl] for pl in plants) * job["sec"] / 60
else:
minutes = math.ceil(n / job["split"]) * job["sec"] / 60
end = start + minutes
done[job["id"]] = end
if "due" in job:
due = job["due"]
elif job["heavy"]:
due = AUGSBURG_DAY if after else ROCKFORD_DAY
else:
due = ROCKFORD_DAY
rows.append({**job, "n": n, "start": start, "end": end, "minutes": minutes,
"due": due, "ok": end <= due})
return rows
def show_checklist() -> None:
print("Calder Pumpworks (fictional): non-functional requirements for CP10's S/4HANA system\n")
area = None
for n in NFRS:
if n["area"] != area:
area = n["area"]
print(area)
print(f" {n['id']} {n['req']:<52} {n['target']}")
print(f" owner: {n['owner']}; delivered by: {n['by']}")
print(f"\n{len(NFRS)} requirements. Next: python unit1b/nfr.py --peaks")
def show_peaks() -> None:
print("Average working day vs last working day (WD-1), documents in CP10's system\n")
print(f"{'Object':<20}{'avg day':>9}{'WD-1':>8}{'peak x':>8}{'WD-1 after rollout':>20}")
for obj in VOLUMES:
avg = VOLUMES[obj]["CP10"] / WORKDAYS
peak = month_end(obj, False)
print(f"{obj:<20}{avg:>9,.0f}{peak:>8,}{peak / avg:>7.1f}x{month_end(obj, True):>20,}")
today, after = MRP_SCOPE["CP11"], sum(MRP_SCOPE.values())
print(f"{'MRP materials':<20}{today:>9,}{today:>8,}{'1.0x':>8}{after:>20,}")
print("\nSize for WD-1, not for the average day: billing on WD-1 is 8.4 times an average day.")
def show_window(after: bool) -> list:
label = "after CP20 joins (July 2027)" if after else "today (CP10 only)"
rule = "00:00 (Augsburg starts work)" if after else "06:00 (Rockford starts work)"
print(f"Month-end night, {label}. Heavy jobs must end by {rule}.\n")
print(f"{'Job':<6}{'units':>7}{'split':>6}{'start':>7}{'end':>7}{'due':>7} result")
rows = schedule(after)
for r in rows:
n = f"{r['n']:,}" if r["n"] is not None else "-"
split = str(r.get("split", "-"))
result = "ok" if r["ok"] else f"LATE by {math.ceil(r['end'] - r['due'])} min"
print(f"{r['id']:<6}{n:>7}{split:>6}{clock(r['start']):>7}{clock(r['end']):>7}"
f"{clock(r['due']):>7} {result}")
late = [r["id"] for r in rows if not r["ok"]]
print(f"\n{len(late)} job(s) late" + (f": {', '.join(late)}" if late else "") + ".")
return late
def show_availability() -> None:
prd = MONTH_MINUTES * (100 - SLA_PRD) / 100
nonprd = MONTH_MINUTES * (100 - SLA_NONPRD) / 100
print("Availability and recovery for CP10's production system (October 2026, 31 days)\n")
print(f" SLA {SLA_PRD}% allows {prd:,.1f} min of downtime a month, outside excluded downtime")
print(f" Non-production at {SLA_NONPRD}% allows {nonprd / 60:,.1f} h a month")
for after in (False, True):
bill = next(r for r in schedule(after) if r["id"] == "BILL")
hit = min(bill["n"], round(bill["n"] * RPO_MIN / bill["minutes"]))
label = "after the rollout" if after else "today"
print(f" A {RPO_MIN}-min data loss during the WD-1 billing run, {label}: "
f"up to {hit:,} billing documents to re-create")
gap = RTO_H - TOLERATED_OUTAGE_H
print(f" Contracted RTO {RTO_H} h vs {TOLERATED_OUTAGE_H} h tolerated at month end: gap of {gap} h")
print(f"\nOpen decision: buy the {RTO_OPTION_H}-hour RTO option, or accept {RTO_H} h "
"with a written fallback for month end.")
def show_ai(calls: int) -> bool:
ok = True
usable = AI["rate_limit_rpm"] * AI["headroom"]
print(f"Invoice triage agent: {calls} model calls per invoice, course limit "
f"{AI['rate_limit_rpm']} requests/min, use at most {usable:.0f}\n")
for after in (False, True):
inv = month_end("Supplier invoices", after)
rpm = inv * AI["peak_share"] * calls / (AI["peak_hours"] * 60)
share = rpm / AI["rate_limit_rpm"]
monthly = (VOLUMES["Supplier invoices"]["CP10"]
+ (VOLUMES["Supplier invoices"]["CP20"] if after else 0))
cost = monthly * calls * AI["cost_per_call"]
state = "ok" if rpm <= usable else "OVER headroom"
ok = ok and rpm <= usable
label = "after rollout" if after else "today"
print(f" {label:<14} WD-1 invoices {inv:>5,} peak {rpm:>5.1f} req/min "
f"({share:.0%} of limit) {state}; cost {cost:,.2f} USD a month")
print(f" Model time per invoice if every call takes its p95: {calls * AI['p95_call_s']:.1f} s")
return ok
def issues() -> list:
found = []
for n in NFRS:
for field in ("target", "measure", "owner", "by", "task"):
if not n.get(field):
found.append(f"{n['id']}: no {field}")
if n.get("task") not in RESP:
found.append(f"{n['id']}: task {n.get('task')!r} has no responsibility rule")
continue
who, ref = RESP[n["task"]]
if n.get("by") and n["by"] != who.rstrip("*"):
found.append(f"{n['id']}: delivered by {n['by']}, but the R&R document says {who} ({ref})")
for after in (False, True):
for r in schedule(after):
if not r["ok"]:
found.append(f"N03: {r['id']} ends {clock(r['end'])}, due {clock(r['due'])} "
f"({'after rollout' if after else 'today'})")
return found
def check() -> int:
found = issues()
ai_ok = all(m <= AI["rate_limit_rpm"] * AI["headroom"] for m in [
month_end("Supplier invoices", a) * AI["peak_share"] * AI["calls_per_invoice"]
/ (AI["peak_hours"] * 60) for a in (False, True)])
if not ai_ok:
found.append("N15: invoice agent above its headroom at peak")
print(f"Checked {len(NFRS)} requirements, {len(JOBS)} month-end jobs (today and after rollout)\n")
for f in found:
print(f" ISSUE {f}")
print(f"\n{len(found)} issue(s).")
return 1 if found else 0
def write() -> None:
rows = schedule(True)
lines = ["# Calder Pumpworks (fictional): non-functional requirements", "",
"Course values and workshop inputs, not SAP standard values. Contract values come from "
"SAP's agreement documents; a real order form can change them.", "",
"| ID | Area | Requirement | Target | Measure | Owner | Delivered by |",
"| --- | --- | --- | --- | --- | --- | --- |"]
for n in NFRS:
lines.append(f"| {n['id']} | {n['area']} | {n['req']} | {n['target']} | {n['measure']} "
f"| {n['owner']} | {n['by']} |")
lines += ["", "## Month-end night after the rollout", "",
"| Job | Start | End | Due | Result |", "| --- | --- | --- | --- | --- |"]
for r in rows:
lines.append(f"| {r['id']} | {clock(r['start'])} | {clock(r['end'])} | {clock(r['due'])} "
f"| {'ok' if r['ok'] else 'late'} |")
lines += ["", f"## Open issues ({len(issues())})", ""] + [f"- {i}" for i in issues()]
lines += ["", "## Decision record", "", "- **Context:**", "- **Options:**",
"- **Decision:**", "- **Consequences:**", ""]
OUT.write_text("\n".join(lines), encoding="utf-8")
print(f"Wrote {OUT}. Open it and fill in the decision record.")
def main() -> None:
p = argparse.ArgumentParser(description="Calder's non-functional requirements (fictional).")
p.add_argument("--peaks", action="store_true", help="average day vs month-end day")
p.add_argument("--window", action="store_true", help="the month-end night")
p.add_argument("--after", action="store_true", help="with --window: after CP20 joins")
p.add_argument("--availability", action="store_true", help="downtime and recovery exposure")
p.add_argument("--ai", action="store_true", help="the invoice agent against its rate limit")
p.add_argument("--calls", type=int, default=AI["calls_per_invoice"], help="with --ai: calls per invoice")
p.add_argument("--check", action="store_true", help="completeness, ownership and targets")
p.add_argument("--write", action="store_true", help="write unit1b/calder_nfr.md")
a = p.parse_args()
load_pack()
if a.peaks:
show_peaks()
elif a.window:
show_window(a.after)
elif a.availability:
show_availability()
elif a.ai:
show_ai(a.calls)
elif a.check:
sys.exit(check())
elif a.write:
write()
else:
show_checklist()
if __name__ == "__main__":
main()
Read the blocks VOLUMES, JOBS, RESP and NFRS first. They are Calder's workshop written as Python data. RESP maps each kind of task to the party SAP's private edition roles and responsibilities document gives it, with the task ID from that document; Calder* means "Calder's, unless bought from SAP as an extra service". Every other value is invented.
You should see the 15 requirements grouped by area. The first lines:
Calder Pumpworks (fictional): non-functional requirements for CP10's S/4HANA system
Volumes
N01 Billing documents on the last working day 2,400 today; 4,000 after the rollout
owner: Order-to-cash lead; delivered by: Calder
Now compare average and peak. Run python unit1b/nfr.py --peaks (use unit1b\nfr.py on Windows, here and below):
Average working day vs last working day (WD-1), documents in CP10's system
Object avg day WD-1 peak x WD-1 after rollout
Sales order items 429 720 1.7x 1,160
Billing documents 286 2,400 8.4x 4,000
Supplier invoices 71 600 8.4x 1,000
Goods movements 1,429 1,800 1.3x 3,000
MRP materials 4,200 4,200 1.0x 7,100
Size for WD-1, not for the average day: billing on WD-1 is 8.4 times an average day.
WD-1 is the last working day of the month, as in Record-to-report end to end. The average day is the monthly volume divided by 21 working days.
Month-end night, today (CP10 only). Heavy jobs must end by 06:00 (Rockford starts work).
Job units split start end due result
BILL 2,400 1 22:00 23:00 06:00 ok
OUT 2,400 1 23:00 00:20 06:00 ok
EDI - - 00:20 00:40 06:00 ok
EXTR - - 23:00 23:35 05:00 ok
MRP 4,200 1 22:30 23:33 06:00 ok
0 job(s) late.
Now the same night after the rollout. Run python unit1b/nfr.py --window --after:
Month-end night, after CP20 joins (July 2027). Heavy jobs must end by 00:00 (Augsburg starts work).
Job units split start end due result
BILL 4,000 1 22:00 23:40 00:00 ok
OUT 4,000 1 23:40 01:54 00:00 LATE by 114 min
EDI - - 01:54 02:14 06:00 ok
EXTR - - 23:40 00:30 05:00 ok
MRP 7,100 1 22:30 00:17 00:00 LATE by 17 min
2 job(s) late: OUT, MRP.
Each job starts at its earliest time or when the jobs it depends on finish, whichever is later. Its duration is units times seconds per unit, divided by the number of parallel jobs (split). Billing output waits for billing; the extraction waits for billing. The heavy jobs, which compete with users, must end before the next site's working day. Today that is 06:00; after the rollout it is midnight, when Augsburg starts at 07:00 its time.
Availability and recovery for CP10's production system (October 2026, 31 days)
SLA 99.7% allows 133.9 min of downtime a month, outside excluded downtime
Non-production at 95.0% allows 37.2 h a month
A 30-min data loss during the WD-1 billing run, today: up to 1,200 billing documents to re-create
A 30-min data loss during the WD-1 billing run, after the rollout: up to 1,200 billing documents to re-create
Contracted RTO 12 h vs 4 h tolerated at month end: gap of 8 h
Open decision: buy the 4-hour RTO option, or accept 12 h with a written fallback for month end.
The percentages, RPO and RTO come from SAP's agreement documents for the private edition; the 4 hours the business tolerates is a Calder workshop input. The documents at risk are the billing run's documents per minute times 30 minutes, at most the whole run.
Invoice triage agent: 3 model calls per invoice, course limit 60 requests/min, use at most 42
today WD-1 invoices 600 peak 7.5 req/min (12% of limit) ok; cost 18.00 USD a month
after rollout WD-1 invoices 1,000 peak 12.5 req/min (21% of limit) ok; cost 30.00 USD a month
Model time per invoice if every call takes its p95: 7.5 s
Try the multi-step design. Run python unit1b/nfr.py --ai --calls 12:
Invoice triage agent: 12 model calls per invoice, course limit 60 requests/min, use at most 42
today WD-1 invoices 600 peak 30.0 req/min (50% of limit) ok; cost 72.00 USD a month
after rollout WD-1 invoices 1,000 peak 50.0 req/min (83% of limit) OVER headroom; cost 120.00 USD a month
Model time per invoice if every call takes its p95: 30.0 s
The peak assumes half of WD-1's invoices arrive in two hours. The limit, price and latency are synthetic course values, not SAP AI Core figures; read your own tenant's limits as Running AI on BTP shows.
Checked 15 requirements, 5 month-end jobs (today and after rollout)
ISSUE N10: delivered by SAP, but the R&R document says Calder (BASIC_1.8.04: SAP's system monitoring excludes interfaces)
ISSUE N03: OUT ends 01:54, due 00:00 (after rollout)
ISSUE N03: MRP ends 00:17, due 00:00 (after rollout)
3 issue(s).
The script ends with exit code 1. That is expected: you fix all three in the exercise. A check that fails a build is how a requirements list stays current.
#Step 9: Write the checklist file and save your work
Run:
Windows (PowerShell):
python unit1b\nfr.py --write
macOS / Linux:
python unit1b/nfr.py --write
You should see Wrote unit1b\calder_nfr.md. Open it and fill in the decision record. on Windows, or the same with / on macOS and Linux.
Open unit1b/calder_nfr.md in VS Code and press Ctrl+Shift+V (Windows, Linux) or Cmd+Shift+V (macOS) to see the tables.
As of October 2026, the same design maps onto SAP's offerings and documents like this:
Contract values. For the private edition, the SLA, the RISE supplement and the disaster recovery service description set availability, backups, RPO and RTO; the roles and responsibilities document lists who does each operational task, by task ID. For the public edition under GROW, the supplement sets 99.9% and the backup scheme. Read the versions your order form references; the ones in this topic's sources are dated 2022 to 2024.
Sizing. Quick Sizer for new volumes, and the S/4HANA sizing report (SAP Note 1872170) for an ECC system that moves (SAP sizing). For Calder, Augsburg's ECC volumes feed CP10's sizing before the rollout.
Jobs. Application jobs and job templates in S/4HANA Cloud, scheduled from the process apps or the Application Jobs app; collective billing split into parallel jobs; MRP Live from the Schedule MRP Runs app (SAP Learning, SAP Learning, SAP Learning).
Retention. SAP ILM for retention rules, archiving, blocking and destruction, and for retiring legacy systems such as CP20's ECC after its history is no longer needed online (SAP ILM).
Audit. Change documents per business object, the Security Audit Log, and Read Access Logging for personal data (SAP, 2025).
Where it sits.CP10's S/4HANA production system in SAP's private cloud, with Rockford's and, from July 2027, Augsburg's users; background jobs for billing, output and MRP; the EDI channel to distributors; an extraction to the data layer; and the invoice agent on SAP BTP calling a model.
Non-functional requirements that decide the design
Requirement
Typical target (course values)
If missed
Peak volume
4,000 billing documents on WD-1 after the rollout
Jobs overrun; users wait
Batch window
Heavy jobs end by 00:00 Rockford time
Augsburg's morning slows; invoices go out late
Response time
95% of order saves under 2 s during the run
Order entry backs up in two sites
Recovery
RPO 30 min, RTO 12 h contracted; 4 h tolerated
A month-end outage lasts three times longer than the business accepts
Audit
Bank changes weekly; emergency sessions within 2 days
Findings, and fraud found late
AI capacity
Peak under 70% of the rate limit
Throttled calls, a late agent, retries that cost money
Integration and identity. Background jobs run under named job users, so the joiner-mover-leaver process must reassign them (Hire-to-retire touchpoints). EDI and the data layer use technical communication users restricted to their released APIs and extraction views. The invoice agent calls S/4HANA with its own identity (AGT_INV_TRIAGE) and proposes; a person approves anything that changes SAP data (Roles and authorization design).
Context: After CP20 joins CP10's system, the quiet window on WD-1 shrinks from 22:00-06:00 to 22:00-00:00 Rockford time. At peak volumes, billing output ends at 01:54 and MRP at 00:17.
Options: (a) split billing and output into parallel jobs and run MRP as one job per plant; (b) start the heavy jobs earlier, during Rockford's evening shift; (c) accept the overlap and size for dialog response times during Augsburg's morning.
Decision: (a), as a course choice, with (c)'s load test as proof. It uses documented options and keeps Rockford's evening untouched.
Consequences: Faster runs put more documents inside a 30-minute RPO window, so the restore checklist must cover the whole billing run. Job users and logs multiply by four. The schedule must be re-checked every time zone change and every volume change.
What a design review will ask
What are the peak volumes after the rollout, and who signed them off?
Which jobs must finish before which users start, and how is that monitored every month end?
Is the contracted RTO shorter than what the business tolerates at month end? If not, what is the decision?
Which tasks in SAP's roles and responsibilities list are Calder's, and who owns each?
How are AI calls kept inside their rate limit at peak, and what happens when they aren't?
Security and authorizations. Job users and communication users hold real authorizations. Give them only what the job needs, and review them with the business roles. Log and review emergency access.
Evaluation. Load-test the month-end night with production-sized data and the batch and dialog load together. Re-run the check whenever a volume, a job or a time zone changes.
Cost. The 4-hour RTO option, extra services from SAP's list and archiving storage all cost money. Put a business figure next to each, such as the cost of a day's delayed billing.
Operations. Alert on job durations, not just failures: a job that runs 30% longer each month fails in three months. Read logs of jobs marked Finished. Rehearse a restore with the process owners.
Clean core. Custom code that reads too much data is the most common performance problem you own. Prefer released APIs and CDS views, test on production-sized data, and review SQL before transport (Fit-to-standard and extension choices).
Fix Calder's three issues, add one requirement, and record the decision.
Open unit1b/nfr.py. In NFRS, change requirement N10 from "by": "SAP" to "by": "Calder".
In JOBS, set "split": 4 for BILL and for OUT, and "split": 2 for MRP (one job per plant).
Run python unit1b/nfr.py --window --after. All five jobs should be ok, with OUT ending at 22:59 and MRP at 23:33.
Run python unit1b/nfr.py --availability. The documents at risk after the rollout should now read up to 4,000. Write one sentence on why a faster run raised the exposure.
In NFRS, add N16: area "Audit", requirement "Every agent proposal and its approver logged", target "Kept as long as the invoice it concerns", measure "Agent log matched to invoices monthly", owner "AI app team", "by": "Calder", "task": "change_docs".
Run python unit1b/nfr.py --check. It should report 0 issue(s) and end with exit code 0.
Run python unit1b/nfr.py --write, open unit1b/calder_nfr.md, and fill in the Decision record on the RTO gap: buy the 4-hour option, or accept 12 hours with a written month-end fallback. Give your reasons.
Commit the script and the file.
Done when:--check reports 0 issue(s) for 16 requirements, --window --after shows every job ok, and unit1b/calder_nfr.md has a filled-in decision record on the RTO gap. Keep it: the capstone at the end of Unit 1B, One exception, traced across the enterprise, traces one exception across the enterprise, and this checklist is its non-functional part.
Pick one answer for each question. The explanation appears after you choose.
1In schedule(), why does OUT start at 23:40 after the rollout rather than at 22:00?
Answer: C. Each job starts at its earliest time or when its dependencies end, whichever is later. With 4,000 documents at 1.5 seconds each, billing runs 100 minutes, so output can't start before 23:40.
2Why does splitting MRP into two jobs bring it to 23:33 and not to half its time?
Answer: A. The split is by plant, so the run takes as long as the largest scope: 4,200 materials at 0.9 seconds is 63 minutes from 22:30. An even split would be faster, but plants are not equal.
3The check flags N10. What does the rule behind that issue protect against?
Answer: D. RESP encodes SAP's roles and responsibilities document. System monitoring excludes interfaces, so a requirement saying SAP delivers interface alerts would leave nobody watching them.
4After the exercise, the documents at risk in a 30-minute data loss rise from 1,200 to 4,000. Why?
Answer: B. Billing now takes 25 minutes, so all 4,000 documents can fall inside one RPO window. Faster batch runs concentrate the exposure, which is why the restore checklist must cover the whole run.
5Calder's business tolerates 4 hours of outage at month end; the contract says 12. What should the architect do?
Answer: C. The DR service offers a 4-hour RTO option at extra cost. Either buying it or accepting 12 hours with a written fallback is defensible; leaving the gap undiscovered is not. Availability percentages don't change recovery time.
6The invoice agent moves from 3 to 12 model calls per invoice. Which number crosses its limit first?
Answer: C. The design keeps peak use under 70% of the rate limit. After the rollout, half of 1,000 invoices in two hours at 12 calls each needs 50 requests a minute, 83% of 60. An overnight agent can tolerate 30 seconds per invoice.
7A custom report runs in 2 seconds in development and 4 minutes in production. What is the most likely cause, and whose task is the fix?
Answer: A. Code that works on 50 test documents and fails on millions usually moves data to the code or calls the database per record. Optimizing expensive SQL is the customer's task in the private edition list unless bought from SAP.
Ask a question
Testing: only staff see this
Stuck on something in this layer? Ask it here. Questions are answered in the order they arrive, and the answer appears under My questions.
Sign in (free) to ask a question. You can ask anonymously.
Related topics
Picked from the terms, links and tags this topic shares with others.
RISE with SAP S/4HANA Cloud, private edition and SAP ERP, PCE: Roles and Responsibilities (SAP, v7 2023)— standard services vs CAS vs excluded tasks; system monitoring excludes interfaces and jobs (BASIC_1.8.04); backups and restore by SAP, logical validation of restores by customer (INFRA_1.8.06); expensive SQL optimization CAS (HANA_1.1.15); capacity reports and plan by SAP; Security Audit Log analysis CAS (BASIC_1.2.19)
Sizing (SAP Benchmarks)— sizing translates business requirements into hardware requirements, iteratively; user-based, throughput-based and customer performance test models; greenfield and brownfield sizing; SAP Note 1872170 S/4HANA sizing report for ECC; Quick Sizer
Creating Billing Documents in Various Ways (SAP Learning)— billing due list (VF04), collective billing in the background, split into several background jobs run simultaneously, logs off for performance, Create Billing Documents app
Planning Using MRP Live (SAP Learning)— MRP Live optimized for SAP HANA, MD01N or Schedule MRP Runs app; one database procedure; doesn't support all processes; classic MRP still used for some materials, chosen automatically