Home/Unit 14 · The FDE operating system and capstone
Technical storytelling: one architecture, five audiences
Explain one AI solution to a CIO, a process owner, a developer, an SAP architect and the security team, each with the right picture, numbers, risks and ask.
A forward deployed engineer explains the same AI solution many times a week. The CIO, the business process owner, a developer, the SAP architect and the security team all need to hear about it. Each asks a different question:
CIO: Is it worth it, is it safe, and does it fit our direction?
Process owner: What changes for my people on Monday?
Developer: What do I build, call and test?
SAP architect: Where does it live, and what does it touch in SAP?
Security team: What data goes where, under whose permissions, and what can go wrong?
Technical storytelling means answering each of those questions from one set of facts. You choose a different picture, different numbers and a different request for each reader. The facts never change between versions. If the CIO hears "350 hours a month" and the process owner hears "about 400", trust is gone for both.
Every version has the same five parts: a headline with the answer first, one picture, the numbers that decide their question, the risks they own, and one ask.
The full, written design still lives in one place: the solution design document. The five stories are short views of it, cut for each reader, not replacements for it.
AI projects rarely stall because the model fails. They stall because an approver heard the wrong story. The CIO got a model diagram and couldn't see the value. The security team got a business case and couldn't see the data flow. The process owner heard "automation" and pictured job cuts.
Take the course's running example. The blocked sales orders assistant has just finished its two-week pilot, as described in Rapid prototyping. In the made-up pilot data:
Analysts with the assistant took a median of 12 minutes per order; analysts without it took 15.5.
The target was 10 minutes. The pilot got 64% of the way there, so gate 3 said ITERATE.
35 of 40 test cases passed, with no critical failures.
Model cost was 0.031 EUR per order.
Now the team needs five things at once: budget for another round from the CIO, analysts from the process owner, fixes from the developers, the architect's sign-off on the design, and a security review. One deck for everyone serves no one. Five unconnected decks drift apart within a week.
Good storytelling also protects the business. It forces the team to state the miss plainly: 64% of the target, not "a success". Decisions made on a polished half-truth come back as failed rollouts.
None of the SAP sources opened for this topic offers a storytelling method for AI projects. As of October 2026, SAP does offer material that makes each audience's version easier to build and easier to trust:
Diagram levels. SAP's BTP solution diagram guideline sorts diagrams by level of detail. draw.io's summary of it describes level 0 as an overview for executives, and level 1 for roles such as product managers, enterprise architects and use case owners. Level 2 adds technical detail such as identities, API calls and firewalls. SAP Architecture Center recommends draw.io with the guideline's shapes for landscapes, and Mermaid for process flows.
Reference architectures. SAP Architecture Center publishes reference architectures, such as one for generative AI on SAP BTP with CAP and SAP AI Core. Starting from it gives SAP architects a picture they already know.
Process models. SAP Signavio Process Manager lets process owners model processes in BPMN. If the customer already has a model of the order-to-cash process, show the change on it.
Answers for the security team. SAP Learning's "Building Confidence in AI with SAP" course states that customer data is not used for training by external model providers when SAP uses their models. SAP's security course adds that customers remain ultimately responsible for legal requirements, personal data and oversight.
Responsible AI. SAP describes its own Global AI Ethics Policy and human-in-the-loop design. That gives the CIO and compliance a shared vocabulary for oversight.
Choosing what each audience hears, and keeping the five versions consistent, is the forward deployed engineer's job.
"Simplifying for executives means leaving out the problems." It means leaving out the mechanics. The problems, and who owns them, stay in.
"One deck with backup slides covers every audience." Each audience needs a different first slide. Backup slides answer follow-up questions; they don't change what the reader sees first.
"Security just needs the architecture diagram." They need the data flow: which fields leave which system, under whose permissions, and what is logged.
"Developers don't need the business numbers." Developers who know the target and the cost per case make better trade-offs than developers who only know the ticket.
"Good storytelling is spin." Spin changes the facts per audience. Storytelling changes the order and the language, never the facts.
Pick one answer for each question. The explanation appears after you choose.
1The CIO and the process owner will both see this week's pilot update. What must be identical in both versions?
Answer: A. The picture, the order and the ask change with the audience, because each has a different question. The facts never change. If two versions disagree on a number, both audiences stop trusting the team.
2The pilot reached 64% of the way to its target. Where should that appear in the CIO's update?
Answer: C. The headline states the answer first, including bad news. Hiding the miss leads to a decision based on a half-truth, and it surfaces later as a failed rollout. The value per month belongs next to it.
3Which picture fits the business process owner best?
Answer: A. The process owner's question is what changes for their people. A before-and-after process view answers it, and shows that the analyst still decides every release. The other pictures answer a developer's, architect's or security team's question.
4The security team asks whether the model provider trains on the customer's order data. What is the best preparation?
Answer: C. SAP Learning states that external providers don't use customer data for training when SAP uses their models, and that customers remain responsible for legal requirements and oversight. The security team needs that statement, its date, and the actual data flow.
5A partner presents one 40-slide deck to the CIO, the security team and the developers together. What is the main problem?
Answer: C. Each audience has a different question, so each needs a different headline, picture and ask. Backup slides can answer follow-ups, but they don't change what each reader sees first.
6Why give developers the business numbers, such as the target time and the cost per order?
Answer: A. A developer who knows the target and the cost per case can weigh a slower but cheaper call, or a fix that matters to the metric. Without them, they optimize the ticket instead of the result.
7What is the clearest sign that a team is not ready for a stakeholder meeting?
Answer: C. Every version of the story ends in one ask: a decision, people, a fix or a review. Without it, the meeting produces no action. Official shapes help with architects, and pilot numbers are exactly what the decision needs.
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.
One model of the solution, many views of it. A view may leave facts out and translate them, but it never adds or changes one.
The C4 model makes the same point about diagrams. A system context diagram shows one system with its users and neighbours, and its audience is everybody, technical or not. A container diagram zooms into the same system for architects, developers and operations staff. Both are views of one model, at different zoom levels.
Storytelling applies that idea to everything you say about a solution, not only the pictures:
Part
Lives in
Changes per audience?
Facts: numbers, data read, data written, risks, owners
One facts file, next to the evidence
No
Derived numbers: hours per month, cost per month, pass rate
Computed once from the facts
No
Headline, picture, choice of numbers, wording, ask
The brief for each audience
Yes
In a real project, the facts come from the solution design document and the evidence of each prototype gate. The design document is the long form for reviewers who read everything; the briefs are the short forms for everyone else.
If two briefs disagree, one of them is wrong. If a brief needs a number the facts file doesn't hold, add it to the facts file first. That rule is what lets you tell five stories without telling five different truths.
flowchart LR
E[Evidence: eval results, pilot log, design doc] --> F[(Facts file)]
F --> D[Derived numbers]
F & D --> B1[CIO brief]
F & D --> B2[Process owner brief]
F & D --> B3[Developer brief]
F & D --> B4[SAP architect brief]
F & D --> B5[Security brief]
B1 & B2 & B3 & B4 & B5 --> C{Check against facts}
Google's technical writing course starts every document by defining the audience. It warns that a role is only a first approximation: two architects can know very different things. It also names the trap experts fall into, the curse of knowledge: forgetting what a newcomer doesn't know. The wider the audience, the more you must explain, and the simpler the words should be.
For each audience, write down four things before you write a word:
Each brief opens with one sentence that answers the reader's question. The rest of the brief supports it. If the news is bad, it goes in the headline: "The pilot saved 3.5 minutes per order, 64% of the way to the target." A reader who stops after one sentence should still have the truth.
Each audience gets one diagram, the one that answers their question. SAP's diagram guidance and C4 point in the same direction:
Audience
Picture
Closest SAP or C4 equivalent
CIO
Context: the user, the new app, SAP, the model
BTP solution diagram level 0 (executive overview); C4 system context
Process owner
The process before and after, with lanes for who acts
A BPMN model, for example in SAP Signavio Process Manager
Developer
The sequence of calls between parts
Mermaid sequence diagram, which SAP Architecture Center recommends for component interactions
SAP architect
The landscape: BTP app, S/4HANA, APIs, model service
BTP solution diagram L1 or L2; C4 container diagram
Security team
Data flow across trust boundaries
BTP solution diagram level 2, which draw.io's summary says covers identities, API calls, firewalls and protocols
SAP Architecture Center suggests draw.io with the BTP solution diagram shapes for landscapes, and Mermaid for process flows and sequence diagrams. For internal working briefs, Mermaid is enough. For a customer's architecture board, redraw the architect's view in the official shapes.
List risks by owner, not by severity alone. The CIO needs to know a risk exists and who owns it. The developer and the security team also need the control that handles it. A risk that appears in no brief has no audience, and in practice no owner.
The last line of every brief is the one thing you want from this reader. "Approve one more two-week pilot round" is an ask. "Let us know your thoughts" is not.
Most of these briefs are spoken, not read. For a ten-minute slot, a simple order works: headline (30 seconds), picture (2 minutes), numbers (2 minutes), risks and owners (2 minutes), ask (30 seconds), then questions. If you can't answer a question, say so and name when you will. A guessed number in a meeting becomes a "fact" in someone's slide by Friday, and then your facts file is no longer the only source.
#Build it yourself: one set of facts, five checked briefs
You will build a command-line tool that reads one facts file about the blocked sales orders assistant, writes five briefs (one per audience), and checks them. The check catches what goes wrong when people edit briefs by hand: a number that isn't in the facts, jargon in a leader's brief, a missing risk owner, a missing ask, a brief that has grown too long.
Before you start: complete Set up your computer for this course and Set up for Unit 14. They create your orchestrate-course folder, its .venv and the unit14 folder. This script uses only Python's built-in modules, so there is nothing new to install, and it doesn't call a model.
flowchart LR
S[sample] --> F[(story/solution.json)]
F --> B[build]
B --> R[story/briefs/*.md]
R --> C[check]
F --> C
In VS Code, right-click the unit14 folder, choose New File, name it story_pack.py, paste the code below and save.
"""Story pack: one set of facts about an AI solution, five briefs for five audiences.
Audiences: cio, process (business process owner), developer, architect (SAP architect),
security (security and data protection team).
Commands:
sample write a made-up facts file for the blocked sales orders example
facts print the facts and the numbers derived from them
brief AUDIENCE print one brief to the terminal
build write all five briefs to the briefs folder next to the facts file
check check the briefs in that folder against the facts
Uses only Python's built-in modules. No account, no network.
"""
import argparse
import json
import re
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
DEFAULT_FACTS = HERE / "story" / "solution.json"
AUDIENCES = ["cio", "process", "developer", "architect", "security"]
LEADERS = {"cio", "process"} # briefs that must avoid technical jargon
MAX_WORDS = {"cio": 260, "process": 300, "developer": 420, "architect": 420, "security": 420}
HEADLINE_MAX = 30
JARGON = ["LLM", "token", "tokens", "OData", "API", "APIs", "embedding", "RAG", "prompt", "prompts",
"CAP", "p95", "latency", "orchestration", "inference", "vector", "JSON", "OAuth", "endpoint"]
# ---------------------------------------------------------------------------
# 1. The made-up facts file. Every number is invented for the exercise; the
# pilot numbers match the course's rapid prototyping topic.
# ---------------------------------------------------------------------------
SAMPLE = {
"name": "Blocked sales orders: context assistant",
"status": "Pilot finished. Gate 3 said ITERATE: one more two-week round aimed at the time saving.",
"problem": "Credit analysts spend a long time on each blocked sales order, mostly gathering context.",
"solution": "The assistant drafts a context note for each blocked order from SAP data. "
"The analyst still decides every release in SAP.",
"currency": "EUR",
"numbers": {
"orders_per_month": 6000,
"baseline_min": 17,
"target_min": 10,
"control_median_min": 15.5,
"assisted_median_min": 12,
"eval_cases": 40,
"eval_passed": 35,
"critical_failures": 0,
"cost_per_order": 0.031,
"slowest_draft_seconds": 6,
"analysts_in_pilot": 5,
},
"reads": ["sales order header and items", "credit limit and exposure", "open items", "sales rep note"],
"writes": "nothing: the assistant never changes SAP data",
"personal_data": "the sales rep note can contain names; names are masked before the model sees the note",
"risks": [
{"id": "R1", "title": "A draft states a fact that is not in the data",
"owner": "Tech lead", "for": ["cio", "process", "developer", "security"],
"control": "every release stays an analyst decision; failing test cases rerun on every change"},
{"id": "R2", "title": "Analysts stop reading drafts carefully",
"owner": "Head of credit management", "for": ["cio", "process"],
"control": "drafts show their sources; spot checks of released orders every month"},
{"id": "R3", "title": "Text in a rep note tries to steer the model",
"owner": "Security lead", "for": ["developer", "security"],
"control": "note text is treated as data; output is checked; the assistant has no tool that writes"},
{"id": "R4", "title": "Names in rep notes reach the model",
"owner": "Data protection officer", "for": ["architect", "security"],
"control": "masking before the model call; logs keep case numbers, not note text"},
{"id": "R5", "title": "The model is retired by its provider",
"owner": "SAP architect", "for": ["cio", "developer", "architect"],
"control": "test set reruns on the replacement model before any switch"},
],
"asks": {
"cio": "Approve one more two-week pilot round with the same five analysts and the same budget.",
"process": "Name the analysts for round 2 and confirm that every release stays an analyst decision.",
"developer": "Fix the failing test cases first, then rerun the full set before round 2 starts.",
"architect": "Confirm the read-only list of SAP data and the model replacement test.",
"security": "Review the masking of rep notes and the steering tests before round 2 starts.",
},
}
# ---------------------------------------------------------------------------
# 2. Derived numbers: computed once, used by every brief and by the check
# ---------------------------------------------------------------------------
def load(path):
path = Path(path)
if not path.exists():
raise SystemExit(f"ERROR facts file not found: {show(path)}. Run the sample command first.")
try:
facts = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
raise SystemExit(f"ERROR {show(path)} is not valid JSON: line {e.lineno}, column {e.colno}: {e.msg}")
for key in ["name", "status", "solution", "numbers", "risks", "asks", "currency"]:
if key not in facts:
raise SystemExit(f"ERROR {show(path)} has no \"{key}\" entry")
needed = ["orders_per_month", "baseline_min", "target_min", "control_median_min", "assisted_median_min",
"eval_cases", "eval_passed", "critical_failures", "cost_per_order"]
missing = [k for k in needed if k not in facts["numbers"]]
if missing:
raise SystemExit(f"ERROR \"numbers\" is missing: {', '.join(missing)}")
for r in facts["risks"]:
bad = [a for a in r.get("for", []) if a not in AUDIENCES]
if bad:
raise SystemExit(f"ERROR risk {r.get('id')} names unknown audiences: {', '.join(bad)}")
return facts
def derive(n):
saved = n["control_median_min"] - n["assisted_median_min"]
gap = n["control_median_min"] - n["target_min"]
return {
"saved_min_per_order": round(saved, 2),
"hours_saved_per_month": round(saved * n["orders_per_month"] / 60),
"pass_rate_pct": round(100 * n["eval_passed"] / n["eval_cases"]),
"failed_cases": n["eval_cases"] - n["eval_passed"],
"share_of_target_pct": round(100 * saved / gap) if gap > 0 else 0,
"min_still_to_target": round(n["assisted_median_min"] - n["target_min"], 2),
"model_cost_per_month": round(n["cost_per_order"] * n["orders_per_month"]),
}
def fmt(x):
"""6000 -> 6,000; 15.5 -> 15.5; 12.0 -> 12; 0.031 -> 0.031"""
if isinstance(x, float) and not x.is_integer():
return f"{x:,.3f}".rstrip("0").rstrip(".")
return f"{int(x):,}"
# ---------------------------------------------------------------------------
# 3. The five briefs. Same facts, different question, picture, numbers and ask.
# ---------------------------------------------------------------------------
def risk_lines(facts, audience, with_control=True):
lines = []
for r in facts["risks"]:
if audience in r.get("for", []):
text = f"- **{r['title']}.** Owner: {r['owner']}."
if with_control:
text += f" Control: {r['control']}."
lines.append(text)
return lines or ["- None tagged for this audience."]
DIAGRAMS = {
"cio": """flowchart LR
A[Credit analyst] --> B[Context assistant on SAP BTP]
B -->|reads only| C[SAP S/4HANA]
B --> D[AI model through SAP AI Core]
A -->|decides every release| C""",
"process": """flowchart LR
subgraph Analyst
A1[Open blocked order] --> A2[Read the draft note] --> A3{Release?}
end
subgraph Assistant
B1[Collect order, credit and rep note] --> B2[Draft the note]
end
A1 --> B1
B2 --> A2
A3 -->|yes or no, in SAP| A4[Order released or kept]""",
"developer": """sequenceDiagram
participant UI as Assistant panel
participant App as CAP service
participant S4 as S/4HANA OData
participant AI as Orchestration
UI->>App: draft(order)
App->>S4: GET order, credit, items (user's rights)
S4-->>App: fields
App->>AI: template + masked fields
AI-->>App: draft JSON
App-->>UI: draft + sources""",
"architect": """flowchart LR
U[Analyst browser] --> W[CAP app on SAP BTP]
W -->|released OData APIs, read only| S[SAP S/4HANA]
W --> O[Generative AI hub orchestration]
O --> M[Foundation model]
W --> L[Logs: case number, cost, result]""",
"security": """flowchart LR
subgraph Customer SAP landscape
S[S/4HANA data]
end
subgraph SAP BTP subaccount
W[Assistant app] --> K[Masking in orchestration]
end
subgraph Model provider
M[Foundation model]
end
S -->|user's own authorizations| W
K -->|masked fields only| M
M -->|draft text, untrusted| W""",
}
def brief(facts, audience):
n, d, cur = facts["numbers"], derive(facts["numbers"]), facts["currency"]
title = {"cio": "CIO", "process": "business process owner", "developer": "developer",
"architect": "SAP architect", "security": "security team"}[audience]
out = [f"# {facts['name']}: for the {title}", ""]
if audience == "cio":
out += [f"**Headline:** The pilot saved {fmt(d['saved_min_per_order'])} minutes per order, "
f"{fmt(d['share_of_target_pct'])}% of the way to the target: about "
f"{fmt(d['hours_saved_per_month'])} hours a month for about {fmt(d['model_cost_per_month'])} "
f"{cur} of model cost.", "",
"## What it is", "", facts["solution"], "",
"## The numbers", "",
f"- Handling time: {fmt(n['assisted_median_min'])} minutes with the assistant against "
f"{fmt(n['control_median_min'])} without, in the same weeks. Target: {fmt(n['target_min'])}.",
f"- At {fmt(n['orders_per_month'])} orders a month, {fmt(d['saved_min_per_order'])} minutes "
f"per order is about {fmt(d['hours_saved_per_month'])} hours of analyst time.",
f"- Status: {facts['status']}",
f"- Quality: {fmt(n['eval_passed'])} of {fmt(n['eval_cases'])} test cases pass, "
f"with {fmt(n['critical_failures'])} critical failures."]
elif audience == "process":
out += [f"**Headline:** Your analysts keep every release decision; the assistant writes the context note, "
f"and in the pilot their median time fell from {fmt(n['control_median_min'])} to "
f"{fmt(n['assisted_median_min'])} minutes.", "",
"## What changes for your team", "",
"- The analyst opens a blocked order and finds a draft note with its sources.",
"- The analyst checks the note, then releases or keeps the order in SAP, as today.",
"- Nothing is released, changed or sent to the customer by the assistant.", "",
"## The numbers", "",
f"- {fmt(n['analysts_in_pilot'])} analysts used it in the pilot.",
f"- {fmt(d['failed_cases'])} of {fmt(n['eval_cases'])} test notes had a mistake; "
f"{fmt(n['critical_failures'])} could have led to a wrong release."]
elif audience == "developer":
out += [f"**Headline:** {fmt(d['failed_cases'])} of {fmt(n['eval_cases'])} evaluation cases fail; fix those, "
f"keep the slowest draft under {fmt(n.get('slowest_draft_seconds', 0))} seconds and keep cost per order "
f"near {fmt(n['cost_per_order'])} {cur}.", "",
"## What it is", "",
"A CAP service reads the order with the user's own SAP authorizations, fills a prompt template, "
"calls the generative AI hub orchestration service with masking on, and returns a draft as JSON "
"with the source fields it used.", "",
"## The numbers", "",
f"- Evaluation: {fmt(n['eval_passed'])} of {fmt(n['eval_cases'])} pass "
f"({fmt(d['pass_rate_pct'])}%), {fmt(n['critical_failures'])} critical.",
f"- Slowest draft in the pilot: {fmt(n.get('slowest_draft_seconds', 0))} seconds.",
f"- Model cost: {fmt(n['cost_per_order'])} {cur} per order.",
f"- Reads: {', '.join(facts.get('reads', []))}. Writes: {facts.get('writes', '')}."]
elif audience == "architect":
out += ["**Headline:** A side-by-side app on SAP BTP that reads S/4HANA through released APIs, "
"writes nothing back, and reaches the model only through SAP AI Core.", "",
"## Placement and dependencies", "",
f"- Reads: {', '.join(facts.get('reads', []))}.",
f"- Writes: {facts.get('writes', '')}.",
"- Nothing is modified inside S/4HANA; see the design document for the clean core rating.",
f"- Volume: {fmt(n['orders_per_month'])} orders a month; model cost about "
f"{fmt(d['model_cost_per_month'])} {cur} a month at {fmt(n['cost_per_order'])} {cur} per order."]
elif audience == "security":
out += ["**Headline:** The assistant reads with each analyst's own SAP authorizations, sends only "
"masked fields to the model, and has no way to change SAP data.", "",
"## Data and access", "",
f"- Data read: {', '.join(facts.get('reads', []))}.",
f"- Personal data: {facts.get('personal_data', '')}.",
f"- Writes: {facts.get('writes', '')}.",
"- Model output is treated as untrusted text and shown to the analyst, never executed.",
f"- Test results: {fmt(n['eval_passed'])} of {fmt(n['eval_cases'])} cases pass, "
f"{fmt(n['critical_failures'])} critical."]
out += ["", "## The picture", "", "```mermaid", DIAGRAMS[audience], "```", "",
"## Risks you own or should know", ""]
out += risk_lines(facts, audience, with_control=audience not in LEADERS)
out += ["", f"**Ask:** {facts['asks'][audience]}", ""]
return "\n".join(out)
# ---------------------------------------------------------------------------
# 4. The check: does each brief still tell the truth, to the right reader?
# ---------------------------------------------------------------------------
NUMBER = re.compile(r"(?<![\w/.-])\d[\d,]*(?:\.\d+)?(?![\w])")
def allowed_numbers(facts):
values = set()
def walk(x):
if isinstance(x, bool):
return
if isinstance(x, (int, float)):
values.add(round(float(x), 3))
elif isinstance(x, str):
for m in NUMBER.findall(x):
values.add(round(float(m.replace(",", "")), 3))
elif isinstance(x, dict):
for v in x.values():
walk(v)
elif isinstance(x, list):
for v in x:
walk(v)
walk(facts)
walk(derive(facts["numbers"]))
return values
def check_brief(text, audience, facts, allowed):
checks = [] # (ok, message)
body = re.sub(r"```.*?```", " ", text, flags=re.S)
words = len(body.split())
head = re.search(r"^\*\*Headline:\*\*(.*)$", text, flags=re.M)
if head:
hw = len(head.group(1).split())
checks.append((hw <= HEADLINE_MAX, f"headline: {hw} words (max {HEADLINE_MAX})"))
else:
checks.append((False, "headline: no line starting with **Headline:**"))
diagrams = len(re.findall(r"^```mermaid", text, flags=re.M))
checks.append((diagrams == 1, f"picture: {diagrams} mermaid diagram(s) (need exactly 1)"))
strays = []
for line in text.splitlines():
if line.startswith("#"):
continue
for m in NUMBER.findall(line):
value = round(float(m.replace(",", "")), 3)
if value not in allowed and not (value.is_integer() and value <= 10):
strays.append(m)
checks.append((not strays, "numbers: all match the facts" if not strays
else f"numbers: not in the facts file: {', '.join(sorted(set(strays)))}"))
if audience in LEADERS:
found = sorted({w for w in JARGON if re.search(rf"(?<![\w-]){re.escape(w)}(?![\w-])", text)})
checks.append((not found, "jargon: none" if not found else f"jargon: {', '.join(found)}"))
for r in facts["risks"]:
if audience in r.get("for", []):
ok = r["title"].lower() in text.lower() and r["owner"].lower() in text.lower()
checks.append((ok, f"risk {r['id']}: {'named with its owner' if ok else 'missing, or owner missing'}"))
ask = re.search(r"^\*\*Ask:\*\*\s*\S", text, flags=re.M)
checks.append((bool(ask), "ask: present" if ask else "ask: no line starting with **Ask:**"))
checks.append((words <= MAX_WORDS[audience], f"length: {words} words outside the diagram "
f"(max {MAX_WORDS[audience]})"))
return checks
# ---------------------------------------------------------------------------
# 5. Commands
# ---------------------------------------------------------------------------
def show(path):
try:
return str(Path(path).resolve().relative_to(Path.cwd().resolve()))
except ValueError:
return str(path)
def cmd_sample(args):
path = Path(args.dir) / "solution.json"
if path.exists() and not args.force:
print(f"{show(path)} already exists. Add --force to overwrite it.")
return 1
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(SAMPLE, indent=2) + "\n", encoding="utf-8")
print(f"Wrote {show(path)} (made-up facts for the blocked sales orders example).")
print("Next: python unit14/story_pack.py facts")
return 0
def cmd_facts(args):
facts = load(args.facts)
print(f"{facts['name']}\n{facts['status']}\n")
print("Facts:")
for k, v in facts["numbers"].items():
print(f" {k:<24} {fmt(v)}")
print("Derived (computed by this script, used in every brief):")
for k, v in derive(facts["numbers"]).items():
print(f" {k:<24} {fmt(v)}")
print(f"Risks: {len(facts['risks'])} Audiences: {', '.join(AUDIENCES)}")
return 0
def cmd_brief(args):
print(brief(load(args.facts), args.audience))
return 0
def cmd_build(args):
facts = load(args.facts)
folder = Path(args.facts).resolve().parent / "briefs"
folder.mkdir(exist_ok=True)
existing = [a for a in AUDIENCES if (folder / f"{a}.md").exists()]
if existing and not args.force:
print(f"Briefs already exist in {show(folder)}: {', '.join(existing)}. "
"Add --force to overwrite them (your edits will be lost).")
return 1
for a in AUDIENCES:
(folder / f"{a}.md").write_text(brief(facts, a), encoding="utf-8")
print(f"Wrote 5 briefs to {show(folder)}: " + ", ".join(f"{a}.md" for a in AUDIENCES))
return 0
def cmd_check(args):
facts = load(args.facts)
folder = Path(args.facts).resolve().parent / "briefs"
allowed = allowed_numbers(facts)
failures = 0
for a in AUDIENCES:
path = folder / f"{a}.md"
if not path.exists():
print(f"== {a}.md\n FAIL file not found. Run the build command first.\n")
failures += 1
continue
print(f"== {a}.md")
for ok, message in check_brief(path.read_text(encoding="utf-8"), a, facts, allowed):
print(f" {'PASS' if ok else 'FAIL'} {message}")
failures += 0 if ok else 1
print()
print(f"Result: {len(AUDIENCES)} briefs, {failures} failure(s)")
return 1 if failures else 0
def main(argv=None):
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
sub = p.add_subparsers(dest="cmd", required=True)
s = sub.add_parser("sample", help="write a made-up facts file")
s.add_argument("--dir", default=str(DEFAULT_FACTS.parent), help="folder (default: unit14/story)")
s.add_argument("--force", action="store_true", help="overwrite an existing facts file")
for name, text in [("facts", "print facts and derived numbers"), ("brief", "print one brief"),
("build", "write the five briefs"), ("check", "check the briefs")]:
c = sub.add_parser(name, help=text)
if name == "brief":
c.add_argument("audience", choices=AUDIENCES)
if name == "build":
c.add_argument("--force", action="store_true", help="overwrite existing briefs")
c.add_argument("--facts", default=str(DEFAULT_FACTS), help="facts file (default: unit14/story/solution.json)")
args = p.parse_args(argv)
handler = {"sample": cmd_sample, "facts": cmd_facts, "brief": cmd_brief,
"build": cmd_build, "check": cmd_check}[args.cmd]
return handler(args) or 0
if __name__ == "__main__":
sys.exit(main())
Wrote unit14/story/solution.json (made-up facts for the blocked sales orders example).
Next: python unit14/story_pack.py facts
Run:
python unit14/story_pack.py facts
You should see the facts, then the numbers the script derives from them:
Blocked sales orders: context assistant
Pilot finished. Gate 3 said ITERATE: one more two-week round aimed at the time saving.
Facts:
orders_per_month 6,000
baseline_min 17
target_min 10
control_median_min 15.5
assisted_median_min 12
eval_cases 40
eval_passed 35
critical_failures 0
cost_per_order 0.031
slowest_draft_seconds 6
analysts_in_pilot 5
Derived (computed by this script, used in every brief):
saved_min_per_order 3.5
hours_saved_per_month 350
pass_rate_pct 88
failed_cases 5
share_of_target_pct 64
min_still_to_target 2
model_cost_per_month 186
Risks: 5 Audiences: cio, process, developer, architect, security
Open unit14/story/solution.json in VS Code. Look at the risks list: each risk has a title, an owner, a control, and a for list naming the audiences that must hear about it. The asks section holds one request per audience.
Notice that hours_saved_per_month assumes the assistant is used on all 6,000 orders. Say so when you show it, as the CIO brief does.
If you see already exists, you ran this before. Add --force to overwrite the facts file.
Wrote 5 briefs to unit14/story/briefs: cio.md, process.md, developer.md, architect.md, security.md
Open unit14/story/briefs/cio.md. It starts like this:
# Blocked sales orders: context assistant: for the CIO
**Headline:** The pilot saved 3.5 minutes per order, 64% of the way to the target: about 350 hours a month for about 186 EUR of model cost.
## What it is
The assistant drafts a context note for each blocked order from SAP data. The analyst still decides every release in SAP.
## The numbers
- Handling time: 12 minutes with the assistant against 15.5 without, in the same weeks. Target: 10.
- At 6,000 orders a month, 3.5 minutes per order is about 350 hours of analyst time.
- Status: Pilot finished. Gate 3 said ITERATE: one more two-week round aimed at the time saving.
- Quality: 35 of 40 test cases pass, with 0 critical failures.
Open security.md next. Same facts, different headline:
**Headline:** The assistant reads with each analyst's own SAP authorizations, sends only masked fields to the model, and has no way to change SAP data.
To see a diagram drawn, copy the lines between ```mermaid and ``` from any brief and paste them into the Mermaid Live Editor at https://mermaid.live.
Compare the five briefs. The CIO and process owner briefs list risks with their owners only. The other three also list the control for each risk, because those readers build or review it.
To print one brief without writing files, run python unit14/story_pack.py brief architect (or cio, process, developer, security).
You should see five blocks like this one, and a result line:
== cio.md
PASS headline: 26 words (max 30)
PASS picture: 1 mermaid diagram(s) (need exactly 1)
PASS numbers: all match the facts
PASS jargon: none
PASS risk R1: named with its owner
PASS risk R2: named with its owner
PASS risk R5: named with its owner
PASS ask: present
PASS length: 190 words outside the diagram (max 260)
...
Result: 5 briefs, 0 failure(s)
The jargon line appears only for the CIO and process owner briefs. Words such as "API", "token", "prompt" or "latency" are fine for a developer and a barrier for a sponsor.
Briefs get edited by hand before meetings. This step shows what the check catches.
Open unit14/story/briefs/cio.md.
In the headline, change about 350 hours a month to about 400 hours a month.
In the first line under What it is, change The assistant drafts to The LLM assistant drafts.
Save the file and run:
python unit14/story_pack.py check
The CIO block now shows two failures:
== cio.md
PASS headline: 26 words (max 30)
PASS picture: 1 mermaid diagram(s) (need exactly 1)
FAIL numbers: not in the facts file: 400
FAIL jargon: LLM
and the last line is Result: 5 briefs, 2 failure(s). The script also ends with exit code 1, so an automated pipeline would stop here.
Throw away your edits by rebuilding:
python unit14/story_pack.py build --force
Without --force, build refuses to overwrite briefs, so it can't silently erase the wording you improved.
#Step 7: Change a fact and see which briefs go stale
Facts change: the analysts timed a few more orders, and the control median moves.
Open unit14/story/solution.json and change "control_median_min": 15.5 to "control_median_min": 16. Save.
Run the check without rebuilding:
python unit14/story_pack.py check
Two briefs now fail:
FAIL numbers: not in the facts file: 15.5, 3.5, 350, 64
FAIL numbers: not in the facts file: 15.5
The first line is the CIO brief, the second the process owner brief. The developer, architect and security briefs never used those numbers, so they still pass. That is the list of people who need a corrected update.
The made-up facts: numbers, data read and written, personal data, risks with owners and audiences, one ask per audience
load
Reads the facts file and stops with a clear error if it is missing, isn't valid JSON, or lacks a needed entry
derive
Computes minutes saved, hours per month, pass rate, failed cases, share of target, minutes still to go and model cost per month
fmt
Formats numbers the same way everywhere, for example 6,000 and 0.031
DIAGRAMS
One Mermaid diagram per audience: context, process lanes, call sequence, landscape, data flow with trust boundaries
brief
Writes one audience's brief: headline, body, numbers, picture, risks (with controls for technical readers), ask
allowed_numbers
Collects every number in the facts and every derived number; whole numbers up to 10 are always allowed, for counts such as "gate 3"
check_brief
Checks headline length, exactly one diagram, numbers against the facts, jargon for leaders, risks with owners, the ask and the length
cmd_build, cmd_check
Write the briefs next to the facts file; check them and return exit code 1 on any failure
The generated sentences are a first draft for the blocked orders example. Some sentences, such as "The analyst opens a blocked order", are written into the script rather than taken from the facts. For your own solution, rewrite those in your own words; the check makes sure the facts survive your rewrite.
Levels. SAP's BTP solution diagram guideline sorts diagrams by detail. draw.io's summary describes level 0 as an overview for executives, level 1 for roles such as product managers, enterprise architects and use case owners, and level 2 for technical depth: certificates, identities, API calls, firewalls and protocols. SAP Architecture Center describes L1 as a high-level overview and L2 as detailed views of service interactions, data flows and integration points.
Tools. SAP Architecture Center recommends draw.io with the BTP solution diagram shapes for landscapes, and Mermaid for process flows and sequence diagrams. draw.io ships an SAP shape library and suggests a legend and numbered steps along a path.
Where to get it. SAP Architecture Center links the BTP solution diagram guideline, its starter kit and its draw.io shape libraries, published in SAP's btp-solution-diagrams repository on GitHub. Take the current version from there before an architecture board.
Two statements are worth having ready for the security team, quoted with their source and date:
In "Does AI Train on My Data?", SAP states that when it uses external foundation models, customer data is not used for model training or retraining by external providers. The same lesson says retention depends on the service and its configuration, and that customers stay responsible for storage, governance and deletion.
In its security architecture course, SAP says customers are ultimately responsible for meeting legal requirements. It also leaves the oversight processes, the handling of personal data and keeping humans in the decision loop with the customer.
Your brief should never stretch these into "SAP handles security". The data flow, the masking, the authorizations and the logs in your design are your team's to explain.
None of the SAP sources opened for this topic offers a method for tailoring one solution's story to different audiences. The five-part brief, the audience table and the check are this course's.
BTP solution diagram level 0 in draw.io with SAP shapes
Architecture board picture
Mermaid landscape for internal review
BTP solution diagram L1 or L2; start from an SAP Architecture Center reference architecture
Process picture
Mermaid flowchart with lanes
BPMN model in SAP Signavio Process Manager, if the customer uses it
Data handling statements
Your design's data flow and controls
SAP Learning's trust lessons and the SAP Trust Center, quoted with dates
Consistent numbers across audiences
One facts file, derived numbers, story_pack.py check
No direct equivalent found in the sources opened
A practical mix: keep the facts file and the briefs in Git next to the evidence, use Mermaid for working briefs, and redraw the architect's and security team's pictures in SAP's official shapes when they go to a customer's architecture board.
A brief is not code, but it carries real customer facts into rooms and inboxes.
Confidentiality. Briefs hold customer numbers and architecture details. Share them only with people who may see them. For your public portfolio, use the made-up sample, never a customer's facts.
Provenance. Every number in the facts file should point to its evidence: the evaluation results, the pilot log, the cost log. Keep the facts file in Git next to them, so a number can be traced months later.
Versioning. Regenerate and recheck the briefs after every gate or design change, and date them. A brief from before the pilot, forwarded after it, is a common source of wrong numbers.
Security review is a review. The security brief opens the conversation; it doesn't replace the formal review, the authorization concept or the data protection assessment.
Honest status. State misses, open risks and unverified claims plainly. If you quote SAP on data handling, quote the page and the date, because terms change.
Accessibility. Don't rely on color alone in diagrams; label the arrows. Use simple words: many readers use English as a second language or a translation tool, as Google's writing course points out.
One deck for all. Everyone sees slide one, and slide one answers one person's question.
Numbers typed by hand. A number retyped into a slide drifts. Generate it from the facts or check it against them.
Burying the miss. "Promising results" instead of "64% of the target" costs you the sponsor's trust when the full number surfaces.
The architect's diagram for the CIO. Twenty boxes and protocol names lose the room. Give the CIO the context view.
The CIO's diagram for security. A box labeled "AI" hides exactly what the security team needs: which fields leave, where they go and under whose identity.
Risks without owners. A risk listed with no owner is a worry, not a plan.
No ask. A meeting without an ask produces goodwill and no decision.
Overclaiming SAP. "SAP guarantees our data is safe" isn't what SAP says. Quote what it says, and own the rest.
Write the facts file and the five briefs for your own solution, the one you prototyped in Rapid prototyping, and get them through the check.
Run python unit14/story_pack.py sample --dir unit14/my_story.
In unit14/my_story/solution.json, replace the name, status, solution, numbers, data read and written, personal data, risks and asks with your solution's facts. If you have no solution of your own, keep the blocked orders numbers but change the pilot results to those in your own unit14/my_prototype/decision.md.
Add at least one risk of your own, with an owner, a control and the audiences it is for.
Rewrite the CIO headline and the process owner headline in your own words. Rewrite every sentence that describes the blocked orders example if your solution is different. Keep every number from the facts.
Create unit14/my_story/talk.md with the CIO version as you would say it in two minutes: headline, one sentence about the picture, three numbers, the risks and owners, the ask.
Save your work:
git add unit14/story_pack.py unit14/story/ unit14/my_story/
git commit -m "Unit 14: facts file, five audience briefs and a two-minute CIO talk"
Done whencheck --facts unit14/my_story/solution.json ends with Result: 5 briefs, 0 failure(s), both rewritten headlines are in your own words, and talk.md ends with the ask. Keep the folder: the capstone at the end of Unit 14 presents its result to these five audiences.
Pick one answer for each question. The explanation appears after you choose.
1A colleague wants the CIO brief to say "about 400 hours a month" because it "sounds better" than the 350 in the facts. What does the course's mental model say?
Answer: C. Every brief is a view of one set of facts. A view can choose and translate numbers, but it can't change them. Editing the facts file to match a wish would break the link to the evidence.
2Why does the script compute "hours saved per month" and "model cost per month" instead of letting each brief state them?
Answer: A. derive computes each number once from the facts, and every brief uses that value. When a fact changes, a rebuild updates every brief that uses it, and the check flags the briefs that weren't rebuilt.
3Which picture best answers the security team's question?
Answer: A. The security team asks what data goes where and under whose permissions. A data flow across boundaries answers that; draw.io's summary of SAP's level 2 likewise covers identities, API calls and firewalls. A box labeled "AI" hides exactly what they need.
4After you change control_median_min from 15.5 to 16, the check fails only for the CIO and process owner briefs. What does that tell you?
Answer: C. The check compares each brief's numbers with the current facts and derived numbers. Only the CIO and process owner briefs used 15.5 and the values derived from it. Rebuild them, and send those two readers a corrected update.
5Why does the check look for jargon only in the CIO and process owner briefs?
Answer: D. Fitting vocabulary to the reader is the core of audience analysis, and Google's writing course warns about the curse of knowledge. Technical terms are precise for developers and architects. For a sponsor, they hide the point.
6The security team asks: "Does the model provider train on our order data?" Which answer is best?
Answer: A. SAP Learning states that external providers don't use customer data for training when SAP uses their models. SAP also says customers stay responsible for legal requirements, storage, governance and oversight. Overclaiming, or denying that data leaves the system, fails the review.
7What is the job of the for list on each risk in the facts file?
Answer: D. Each risk has an owner and a for list. The check fails a brief that drops a risk tagged for its audience, or drops its owner. A risk that reaches no audience has, in practice, no owner.
8A partner wants to present the SAP architect's level 2 diagram to the CIO "because it is the most complete". What would you do?
Answer: B. Each audience gets the picture that answers their question. Level 2 detail such as identities and API calls serves architects. The CIO needs the one-page context: users, the new app, SAP and the model.
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.
Diagrams (SAP Architecture Center, community guidelines)— L1 as a high-level overview, L2 for service interactions, data flows and integration points; draw.io for BTP landscapes following the BTP solution diagram guideline; Mermaid flowcharts for processes and sequence diagrams for component interactions
New built-in SAP shape library for BTP solution diagrams (draw.io blog)— summary of the BTP solution diagram levels; level 0 overview for executives, level 1 for product managers, enterprise architects and use case owners, level 2 with certificates, identities, API calls, firewalls and protocols; legends and numbered paths
System context diagram (C4 model)— one system, its users and the systems it talks to; audience is everybody, technical and non-technical
Container diagram (C4 model)— deployable units inside the system; audience is technical people, including architects, developers and operations staff
Audience (Google Technical Writing One)— role is a first approximation; list what the reader must know; the curse of knowledge; a wider audience needs more explanation and simpler words
Does AI Train on My Data? (SAP Learning, Building Confidence in AI with SAP)— SAP states customer data is not used for training by external model providers; opt-in learning features are the customer's choice; retention depends on the service; customers remain responsible for storage, governance and deletion