diff --git a/OR_HTML_04/Operations_Research_mit_Python_Version_04.pdf b/OR_HTML_04/Operations_Research_mit_Python_Version_04.pdf
index bd41dc8..881a63c 100644
Binary files a/OR_HTML_04/Operations_Research_mit_Python_Version_04.pdf and b/OR_HTML_04/Operations_Research_mit_Python_Version_04.pdf differ
diff --git a/OR_HTML_04/bilder_04/kap_supplychain_wirkung.png b/OR_HTML_04/bilder_04/kap_supplychain_wirkung.png
new file mode 100644
index 0000000..91c255f
Binary files /dev/null and b/OR_HTML_04/bilder_04/kap_supplychain_wirkung.png differ
diff --git a/OR_HTML_04/bilder_04/kap_supplychain_wirkung.svg b/OR_HTML_04/bilder_04/kap_supplychain_wirkung.svg
new file mode 100644
index 0000000..628d95b
--- /dev/null
+++ b/OR_HTML_04/bilder_04/kap_supplychain_wirkung.svg
@@ -0,0 +1,2093 @@
+
+
+
diff --git a/OR_HTML_04/gesamtdokument.html b/OR_HTML_04/gesamtdokument.html
index b9bfcb8..c3efb2e 100644
--- a/OR_HTML_04/gesamtdokument.html
+++ b/OR_HTML_04/gesamtdokument.html
@@ -21467,6 +21467,16 @@ Was Versorgungssicherheit kostet
Der Erwartungswert-Plan kostet in der Wirklichkeit 149 % mehr, und sein schlimmster Tag ist sechsmal so teuer.
+
Wofür genau? Die Summendifferenz von 523 514 € sagt noch nicht, wo sie entsteht. Zerlegt man sie nach Kostenarten, wird aus dem Befund eine Aussage, die man vorlegen kann:
+
+
+Abb. 17.1: Wofür der Erwartungswert-Plan 149 % mehr bezahlt
+
+
Drei Dinge stehen darin, die die Tabelle nicht zeigt. Die Anfahrkosten sind in beiden Plänen gleich (58 000 €) — der Erwartungswert-Plan spart also nicht dort, wo man es vermuten würde. Er spart 12 725 € Brennstoff, weil er weniger Blockstunden vorhält. Und er bezahlt dafür 536 239 € Lastabwurf: das 42-fache seiner Ersparnis.
+
Genau diese Gegenüberstellung ist es, die eine Investitionsentscheidung trägt. „149 % mehr” ist eine Zahl, über die man streiten kann; „wir sparen 12 725 € und riskieren dafür 536 239 €” ist eine, über die man entscheidet.
+
+
📌 Zum Diagramm Es wird von bilder_04/erzeuge_wirkung.py erzeugt, das die Instanz aus Kraftwerkseinsatz.pyerneut rechnet statt Zahlen zu übernehmen — und dabei die beiden abgedruckten Summen exakt reproduziert. Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz; das Buchprogramm braucht sie nicht.
+
Der Unterschied zwischen den beiden Plänen ist dabei erstaunlich klein:
diff --git a/OR_HTML_04/supplychain.html b/OR_HTML_04/supplychain.html
index 8e740e3..7886367 100644
--- a/OR_HTML_04/supplychain.html
+++ b/OR_HTML_04/supplychain.html
@@ -772,6 +772,16 @@ Was Versorgungssicherheit kostet
Der Erwartungswert-Plan kostet in der Wirklichkeit 149 % mehr, und sein schlimmster Tag ist sechsmal so teuer.
+
Wofür genau? Die Summendifferenz von 523 514 € sagt noch nicht, wo sie entsteht. Zerlegt man sie nach Kostenarten, wird aus dem Befund eine Aussage, die man vorlegen kann:
+
+
+Abb. 17.1: Wofür der Erwartungswert-Plan 149 % mehr bezahlt
+
+
Drei Dinge stehen darin, die die Tabelle nicht zeigt. Die Anfahrkosten sind in beiden Plänen gleich (58 000 €) — der Erwartungswert-Plan spart also nicht dort, wo man es vermuten würde. Er spart 12 725 € Brennstoff, weil er weniger Blockstunden vorhält. Und er bezahlt dafür 536 239 € Lastabwurf: das 42-fache seiner Ersparnis.
+
Genau diese Gegenüberstellung ist es, die eine Investitionsentscheidung trägt. „149 % mehr” ist eine Zahl, über die man streiten kann; „wir sparen 12 725 € und riskieren dafür 536 239 €” ist eine, über die man entscheidet.
+
+
📌 Zum Diagramm Es wird von bilder_04/erzeuge_wirkung.py erzeugt, das die Instanz aus Kraftwerkseinsatz.pyerneut rechnet statt Zahlen zu übernehmen — und dabei die beiden abgedruckten Summen exakt reproduziert. Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz; das Buchprogramm braucht sie nicht.
+
Der Unterschied zwischen den beiden Plänen ist dabei erstaunlich klein:
diff --git a/Operations_Research_mit_Python_Version_04.md b/Operations_Research_mit_Python_Version_04.md
index 15ff79c..c1e17a9 100644
--- a/Operations_Research_mit_Python_Version_04.md
+++ b/Operations_Research_mit_Python_Version_04.md
@@ -17515,6 +17515,16 @@ Was Versorgungssicherheit kostet
Der Erwartungswert-Plan kostet in der Wirklichkeit **149 % mehr**, und sein schlimmster Tag ist sechsmal so teuer.
+**Wofür genau?** Die Summendifferenz von 523 514 € sagt noch nicht, wo sie entsteht. Zerlegt man sie nach Kostenarten, wird aus dem Befund eine Aussage, die man vorlegen kann:
+
+
+
+Drei Dinge stehen darin, die die Tabelle nicht zeigt. Die **Anfahrkosten sind in beiden Plänen gleich** (58 000 €) — der Erwartungswert-Plan spart also nicht dort, wo man es vermuten würde. Er spart **12 725 € Brennstoff**, weil er weniger Blockstunden vorhält. Und er bezahlt dafür **536 239 € Lastabwurf**: das **42-fache** seiner Ersparnis.
+
+Genau diese Gegenüberstellung ist es, die eine Investitionsentscheidung trägt. „149 % mehr" ist eine Zahl, über die man streiten kann; „wir sparen 12 725 € und riskieren dafür 536 239 €" ist eine, über die man entscheidet.
+
+> **📌 Zum Diagramm** Es wird von `bilder_04/erzeuge_wirkung.py` erzeugt, das die Instanz aus `Kraftwerkseinsatz.py` **erneut rechnet** statt Zahlen zu übernehmen — und dabei die beiden abgedruckten Summen exakt reproduziert. Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz; das Buchprogramm braucht sie nicht.
+
Der Unterschied zwischen den beiden Plänen ist dabei erstaunlich klein:
```
diff --git a/Operations_Research_mit_Python_Version_04.pdf b/Operations_Research_mit_Python_Version_04.pdf
index bd41dc8..881a63c 100644
Binary files a/Operations_Research_mit_Python_Version_04.pdf and b/Operations_Research_mit_Python_Version_04.pdf differ
diff --git a/Operations_Research_mit_Python_Version_04/36_Supply_Chain_Energie.md b/Operations_Research_mit_Python_Version_04/36_Supply_Chain_Energie.md
index 8b2cf2a..d07cbb4 100644
--- a/Operations_Research_mit_Python_Version_04/36_Supply_Chain_Energie.md
+++ b/Operations_Research_mit_Python_Version_04/36_Supply_Chain_Energie.md
@@ -595,6 +595,26 @@ Was Versorgungssicherheit kostet
Der Erwartungswert-Plan kostet in der Wirklichkeit **149 % mehr**, und sein schlimmster Tag
ist sechsmal so teuer.
+**Wofür genau?** Die Summendifferenz von 523 514 € sagt noch nicht, wo sie entsteht. Zerlegt
+man sie nach Kostenarten, wird aus dem Befund eine Aussage, die man vorlegen kann:
+
+
+
+Drei Dinge stehen darin, die die Tabelle nicht zeigt. Die **Anfahrkosten sind in beiden
+Plänen gleich** (58 000 €) — der Erwartungswert-Plan spart also nicht dort, wo man es
+vermuten würde. Er spart **12 725 € Brennstoff**, weil er weniger Blockstunden vorhält. Und
+er bezahlt dafür **536 239 € Lastabwurf**: das **42-fache** seiner Ersparnis.
+
+Genau diese Gegenüberstellung ist es, die eine Investitionsentscheidung trägt. „149 % mehr"
+ist eine Zahl, über die man streiten kann; „wir sparen 12 725 € und riskieren dafür 536 239 €"
+ist eine, über die man entscheidet.
+
+> **📌 Zum Diagramm**
+> Es wird von `bilder_04/erzeuge_wirkung.py` erzeugt, das die Instanz aus
+> `Kraftwerkseinsatz.py` **erneut rechnet** statt Zahlen zu übernehmen — und dabei die
+> beiden abgedruckten Summen exakt reproduziert. Die Trennung von Brennstoff- und
+> Abwurfkosten ist der einzige Zusatz; das Buchprogramm braucht sie nicht.
+
Der Unterschied zwischen den beiden Plänen ist dabei erstaunlich klein:
```
diff --git a/PROGRESS.md b/PROGRESS.md
index b522921..7ef4e5e 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -77,7 +77,7 @@ sie unerwartet ab, ist etwas kaputtgegangen.
| Kapiteldateien | 22 | **31** |
| Kapitel | 15 | **23** |
| Anhänge | 4 | **5** |
-| Zeilen im Gesamtdokument | 11 082 | **28 243** |
+| Zeilen im Gesamtdokument | 11 082 | **28 253** |
| Größe des Gesamtdokuments | 606 KB | **1 593 KB** |
| Hauptüberschriften | 131 | **298** |
| registrierte Abschnitte | 122 | **295** |
@@ -87,7 +87,7 @@ sie unerwartet ab, ist etwas kaputtgegangen.
| PDF-Seiten | — | **746** |
| Notebooks | — | **25** |
| Plotly-Figuren | — | **4** |
-| Diagramme (SVG) | 26 | **32**, davon **18** mit Generatorskript (15 Skripte) |
+| Diagramme (SVG) | 26 | **33**, davon **19** mit Generatorskript (16 Skripte) |
**Gegenprobe:** `build_version_03.py --check` läuft unverändert durch — Version 03 ist von
allen Änderungen unberührt.
@@ -1652,6 +1652,44 @@ aus {ref:sec:praxisfallen-attribution}.
Kein neues Programm, keine neue Aufgabe — der Abschnitt bündelt Vorhandenes. Querverweise
730 → 755, PDF 744 → **746** Seiten.
+### ✅ 7.2 Wasserfalldiagramm „was es bringt" ({ref:kap:supplychain})
+
+Setzt den umsetzbaren Teil von Paket 3 aus `Verbesserungen_03.md` um (Vorher-Nachher-
+Wasserfall, Business Impact). Neuer Generator `bilder_04/erzeuge_wirkung.py`; das
+Supply-Chain-Kapitel bekommt damit **sein erstes Diagramm** überhaupt.
+
+**Der Anlass:** Die Tabelle im Kapitel nannte nur die Summen — 874 870 € gegen 351 356 €,
+149 % mehr. Wofür genau, stand nirgends.
+
+**Der Generator rechnet die Instanz erneut**, wie es die Konvention verlangt („so können
+Diagramm und Buchtext nicht auseinanderlaufen"). Die Probe fiel gut aus: Der unabhängige
+Nachbau reproduziert beide abgedruckten Summen **exakt**, dazu 178,7 MWh Fehlmenge und
+28/40 Szenarien mit Abwurf. Kein Auseinanderlaufen — der erste Generator dieser Reihe, der
+nichts findet, und das ist ein Ergebnis für sich.
+
+**Was die Zerlegung zeigt und die Tabelle nicht:**
+
+| | Anfahrt | Brennstoff | Lastabwurf | Gesamt |
+| --- | ---: | ---: | ---: | ---: |
+| zweistufig | 58 000 € | 293 356 € | 0 € | **351 356 €** |
+| Erwartungswert | 58 000 € | 280 631 € | 536 239 € | **874 870 €** |
+
+Die **Anfahrkosten sind identisch** — der Erwartungswert-Plan spart also nicht dort, wo man
+es vermuten würde. Er spart 12 725 € Brennstoff und bezahlt dafür 536 239 € Lastabwurf: das
+**42-fache**. „149 % mehr" ist eine Zahl, über die man streiten kann; „wir sparen 12 725 €
+und riskieren 536 239 €" ist eine, über die man entscheidet.
+
+Die Trennung von Brennstoff- und Abwurfkosten ist der einzige Zusatz gegenüber `bewerte()`
+im Buchprogramm; dort wird sie nicht gebraucht.
+
+**Ein eigener Fehler unterwegs:** Ein `.replace(",", ".")` für deutsche Tausenderpunkte lag
+auf dem **ganzen Titelsatz** und machte aus „Brennstoff, Preis" ein „Brennstoff. Preis".
+Jetzt gibt es `euro()`, das nur auf Zahlen angewandt wird — mit einem Kommentar, der genau
+davor warnt.
+
+SVG byteidentisch über zwei Läufe. Diagramme jetzt **33**, davon **19** mit Generator
+(16 Skripte).
+
---
## 8. Commit-Historie des V04-Strangs
diff --git a/bilder_04/erzeuge_wirkung.py b/bilder_04/erzeuge_wirkung.py
new file mode 100644
index 0000000..cbee3b2
--- /dev/null
+++ b/bilder_04/erzeuge_wirkung.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+
+# erzeuge_wirkung.py
+"""
+Erzeugt das Wasserfalldiagramm zum Kapitel Supply-Chain und Energieeinsatz:
+
+ bilder_04/kap_supplychain_wirkung.svg statisch, fuer das PDF
+ bilder_04/kap_supplychain_wirkung.png Rasterfassung
+
+Die Frage, die es beantwortet, ist die des Managements: WOFUER genau kostet
+der Erwartungswert-Plan 149 % mehr? Die Tabelle im Kapitel nennt nur die
+Summen (874.870 EUR gegen 351.356 EUR). Der Wasserfall zerlegt die
+Differenz - und die Zerlegung ist der eigentliche Befund:
+
+ * Die ANFAHRKOSTEN sind in beiden Plaenen gleich (58.000 EUR). Der
+ Erwartungswert-Plan spart also nicht dort, wo man es vermuten wuerde.
+ * Er spart 12.725 EUR Brennstoff, weil er weniger Blockstunden vorhaelt.
+ * Und zahlt dafuer 536.239 EUR Lastabwurf.
+
+Das ist das 42-fache dessen, was er einspart. Genau diese Gegenueberstellung
+laesst sich in eine Vorlage uebernehmen, eine Summendifferenz nicht.
+
+Verfahren und Instanz sind wortgleich zu Kraftwerkseinsatz.py, und gerechnet
+wird hier ERNEUT - so koennen Diagramm und Buchtext nicht auseinanderlaufen.
+Der Nachbau reproduziert die im Kapitel abgedruckten Summen exakt; das ist
+die Probe darauf, dass beide dasselbe Modell meinen. Zusaetzlich trennt er
+Brennstoff- und Abwurfkosten, was bewerte() im Buchprogramm nicht tut, weil
+es dort nicht gebraucht wird.
+
+Aufruf (aus dem Repository-Wurzelverzeichnis):
+ python3 bilder_04/erzeuge_wirkung.py
+
+Benoetigt: numpy, matplotlib, ortools
+"""
+
+from __future__ import annotations
+
+import os
+
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+import numpy as np
+from ortools.linear_solver import pywraplp
+
+# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
+plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
+
+BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BILDER = os.path.join(BASIS, "bilder_04")
+
+INDIGO, CYAN, AMBER, GRUEN = "#4338ca", "#0891b2", "#b45309", "#059669"
+GRAU = "#64748b"
+
+# --- Instanz: wortgleich zu Kraftwerkseinsatz.py ----------------------------
+# (Name, Mindestleistung, Nennleistung, Grenzkosten, Anfahrkosten, Mindestlaufzeit)
+KRAFTWERKE = [
+ ("Kernblock", 200, 600, 22, 40_000, 8),
+ ("Braunkohle", 100, 400, 35, 18_000, 6),
+ ("Steinkohle", 80, 300, 48, 12_000, 4),
+ ("Gas GuD", 50, 250, 72, 6_000, 2),
+ ("Gasturbine", 20, 150, 105, 1_500, 1),
+]
+STUNDEN, SZENARIEN, FLAUTENANTEIL, SAAT = 24, 40, 0.15, 4
+LASTABWURF = 3_000.0 # EUR je nicht gedeckter MWh
+
+_t = np.arange(STUNDEN)
+LAST = 620 + 260 * np.sin((_t - 7) / 24 * 2 * np.pi) + 90 * np.sin((_t - 4) / 12 * 2 * np.pi)
+WIND_ERWARTUNG = np.maximum(0.0, 160 + 130 * np.sin((_t - 14) / 24 * 2 * np.pi))
+
+
+def euro(betrag: float, vorzeichen: bool = False) -> str:
+ """Deutsche Tausenderpunkte. Nur auf ZAHLEN anwenden, nie auf ganze Saetze."""
+ text = f"{betrag:+,.0f}" if vorzeichen else f"{betrag:,.0f}"
+ return text.replace(",", ".") + " €"
+
+
+def windszenarien() -> tuple[np.ndarray, np.ndarray]:
+ rng = np.random.default_rng(SAAT)
+ niveau = rng.lognormal(0, 0.40, (SZENARIEN, 1))
+ wind = np.clip(WIND_ERWARTUNG * niveau
+ + rng.normal(0, 25, (SZENARIEN, STUNDEN)), 0, 420)
+ flaute = rng.random(SZENARIEN) < FLAUTENANTEIL
+ wind[flaute] *= 0.05
+ return wind, flaute
+
+
+def plane(wind: np.ndarray, gewichte: np.ndarray) -> np.ndarray:
+ """Stufe 1 (An/Aus, szenariouebergreifend) und Stufe 2 (Fahrweise je
+ Szenario) in einem Modell - wie plane() in Kraftwerkseinsatz.py."""
+ n_s, n_b = wind.shape[0], len(KRAFTWERKE)
+ s = pywraplp.Solver.CreateSolver("SCIP")
+ s.SetTimeLimit(300_000)
+ laeuft = [[s.BoolVar(f"l{k}_{t}") for t in range(STUNDEN)] for k in range(n_b)]
+ start = [[s.BoolVar(f"s{k}_{t}") for t in range(STUNDEN)] for k in range(n_b)]
+ p = [[[s.NumVar(0, KRAFTWERKE[k][2], f"p{j}_{k}_{t}") for t in range(STUNDEN)]
+ for k in range(n_b)] for j in range(n_s)]
+ y = [[s.NumVar(0, s.infinity(), f"y{j}_{t}") for t in range(STUNDEN)]
+ for j in range(n_s)]
+
+ for k, (_, pmin, pmax, _, _, mindestlaufzeit) in enumerate(KRAFTWERKE):
+ for t in range(STUNDEN):
+ vorher = laeuft[k][t - 1] if t > 0 else 0
+ s.Add(start[k][t] >= laeuft[k][t] - vorher)
+ for spaeter in range(t, min(STUNDEN, t + mindestlaufzeit)):
+ s.Add(laeuft[k][spaeter] >= start[k][t])
+ for j in range(n_s):
+ s.Add(p[j][k][t] >= pmin * laeuft[k][t])
+ s.Add(p[j][k][t] <= pmax * laeuft[k][t])
+ for j in range(n_s):
+ for t in range(STUNDEN):
+ s.Add(sum(p[j][k][t] for k in range(n_b))
+ + float(wind[j, t]) + y[j][t] >= LAST[t])
+
+ s.Minimize(
+ sum(KRAFTWERKE[k][4] * start[k][t]
+ for k in range(n_b) for t in range(STUNDEN))
+ + sum(gewichte[j] * sum(KRAFTWERKE[k][3] * p[j][k][t]
+ for k in range(n_b) for t in range(STUNDEN))
+ for j in range(n_s))
+ + sum(gewichte[j] * LASTABWURF * sum(y[j][t] for t in range(STUNDEN))
+ for j in range(n_s)))
+ s.Solve()
+ return np.array([[laeuft[k][t].solution_value() for t in range(STUNDEN)]
+ for k in range(n_b)]).round()
+
+
+def zerlege(plan: np.ndarray, wind: np.ndarray) -> dict:
+ """Setzt einen fertigen Plan der Wirklichkeit aus und trennt dabei die
+ Kostenarten - das ist der Zusatz gegenueber bewerte() im Buchprogramm."""
+ n_b = len(KRAFTWERKE)
+ brennstoff, abwurf, fehlmenge = [], [], []
+ for j in range(wind.shape[0]):
+ s = pywraplp.Solver.CreateSolver("GLOP")
+ p = [[s.NumVar(0, KRAFTWERKE[k][2], f"p{k}_{t}") for t in range(STUNDEN)]
+ for k in range(n_b)]
+ y = [s.NumVar(0, s.infinity(), f"y{t}") for t in range(STUNDEN)]
+ for k, (_, pmin, pmax, _, _, _) in enumerate(KRAFTWERKE):
+ for t in range(STUNDEN):
+ s.Add(p[k][t] >= pmin * plan[k, t])
+ s.Add(p[k][t] <= pmax * plan[k, t])
+ for t in range(STUNDEN):
+ s.Add(sum(p[k][t] for k in range(n_b))
+ + float(wind[j, t]) + y[t] >= LAST[t])
+ s.Minimize(sum(KRAFTWERKE[k][3] * p[k][t]
+ for k in range(n_b) for t in range(STUNDEN))
+ + sum(LASTABWURF * y[t] for t in range(STUNDEN)))
+ s.Solve()
+ brennstoff.append(sum(KRAFTWERKE[k][3] * p[k][t].solution_value()
+ for k in range(n_b) for t in range(STUNDEN)))
+ fehlt = sum(v.solution_value() for v in y)
+ fehlmenge.append(fehlt)
+ abwurf.append(LASTABWURF * fehlt)
+
+ anfahrt = sum(KRAFTWERKE[k][4]
+ * max(0.0, plan[k, t] - (plan[k, t - 1] if t > 0 else 0.0))
+ for k in range(n_b) for t in range(STUNDEN))
+ fehlmenge = np.array(fehlmenge)
+ return {"anfahrt": float(anfahrt),
+ "brennstoff": float(np.mean(brennstoff)),
+ "abwurf": float(np.mean(abwurf)),
+ "fehlmenge": float(fehlmenge.mean()),
+ "szenarien_mit_abwurf": int((fehlmenge > 1e-6).sum()),
+ "gesamt": float(anfahrt + np.mean(brennstoff) + np.mean(abwurf))}
+
+
+def zeichne(zwei: dict, erw: dict) -> None:
+ """Wasserfall vom guten zum schlechten Plan: Was wird gespart, was kostet es."""
+ d_anfahrt = erw["anfahrt"] - zwei["anfahrt"]
+ d_brennstoff = erw["brennstoff"] - zwei["brennstoff"]
+ d_abwurf = erw["abwurf"] - zwei["abwurf"]
+
+ stufen = [("Zweistufiger\nPlan", zwei["gesamt"], None),
+ ("Brennstoff", d_brennstoff, "delta"),
+ ("Anfahren", d_anfahrt, "delta"),
+ ("Lastabwurf", d_abwurf, "delta"),
+ ("Erwartungswert-\nPlan", erw["gesamt"], None)]
+
+ figur, achse = plt.subplots(figsize=(10.2, 5.6))
+ unten = 0.0
+ for i, (name, wert, art) in enumerate(stufen):
+ if art is None:
+ achse.bar(i, wert, 0.62, color=INDIGO, zorder=3)
+ achse.text(i, wert + 18_000, euro(wert),
+ ha="center", va="bottom", fontsize=11, fontweight="bold",
+ color=INDIGO)
+ unten = wert
+ continue
+ farbe = AMBER if wert > 0 else GRUEN
+ start = unten if wert > 0 else unten + wert
+ achse.bar(i, abs(wert), 0.62, bottom=start, color=farbe, zorder=3)
+ # Nullbalken (hier: Anfahren) sichtbar machen, sonst fehlt die Aussage
+ if abs(wert) < 5_000:
+ achse.plot([i - 0.31, i + 0.31], [start, start], color=GRAU,
+ linewidth=2.2, zorder=4)
+ achse.text(i, max(start + abs(wert), unten) + 18_000,
+ euro(wert, vorzeichen=True),
+ ha="center", va="bottom", fontsize=10.5, color=farbe,
+ fontweight="bold")
+ unten = unten + wert
+ if i < len(stufen) - 1:
+ achse.plot([i - 0.31, i + 0.69], [unten, unten], color=GRAU,
+ linewidth=0.9, linestyle=":", zorder=2)
+
+ achse.set_xticks(range(len(stufen)))
+ achse.set_xticklabels([n for n, _, _ in stufen], fontsize=10.5)
+ achse.set_ylabel("Erwartete Tageskosten")
+ achse.set_ylim(0, erw["gesamt"] * 1.18)
+ achse.yaxis.set_major_formatter(
+ plt.FuncFormatter(lambda v, _: f"{v/1000:,.0f}k €".replace(",", ".")))
+ achse.grid(axis="y", linestyle=":", alpha=0.45, zorder=0)
+ for rand in ("top", "right"):
+ achse.spines[rand].set_visible(False)
+
+ verhaeltnis = abs(d_abwurf / d_brennstoff) if d_brennstoff else float("inf")
+ # Zahlen einzeln setzen: Ein .replace(",", ".") auf den ganzen Satz wuerde
+ # auch das Satzkomma treffen.
+ achse.set_title(
+ "Was der Erwartungswert-Plan spart — und was er dafür bezahlt\n"
+ f"Ersparnis {euro(abs(d_brennstoff))} Brennstoff, Preis "
+ f"{euro(d_abwurf)} Lastabwurf: das {verhaeltnis:.0f}-fache",
+ fontsize=12.5, pad=14)
+ achse.text(0.5, -0.155,
+ f"Lastabwurf in {erw['szenarien_mit_abwurf']} von {SZENARIEN} "
+ f"Szenarien statt in {zwei['szenarien_mit_abwurf']}; "
+ f"Anfahrkosten in beiden Plänen gleich.",
+ transform=achse.transAxes, ha="center", fontsize=10, color=GRAU)
+
+ figur.tight_layout()
+ os.makedirs(BILDER, exist_ok=True)
+ for endung in ("svg", "png"):
+ pfad = os.path.join(BILDER, f"kap_supplychain_wirkung.{endung}")
+ figur.savefig(pfad, format=endung, dpi=160,
+ metadata={"Date": None} if endung == "svg" else None)
+ print(f"geschrieben: {pfad}")
+ plt.close(figur)
+
+
+if __name__ == "__main__":
+ wind, flaute = windszenarien()
+ gleich = np.full(SZENARIEN, 1.0 / SZENARIEN)
+ print(f"{SZENARIEN} Windszenarien, davon {int(flaute.sum())} Dunkelflauten")
+
+ erw = zerlege(plane(WIND_ERWARTUNG.reshape(1, -1), np.array([1.0])), wind)
+ zwei = zerlege(plane(wind, gleich), wind)
+
+ for name, w in (("Erwartungswert", erw), ("zweistufig", zwei)):
+ print(f" {name:<16} Anfahrt {w['anfahrt']:>9,.0f} "
+ f"Brennstoff {w['brennstoff']:>9,.0f} "
+ f"Abwurf {w['abwurf']:>9,.0f} = {w['gesamt']:>9,.0f}")
+ print(f" Aufschlag des Erwartungswert-Plans: "
+ f"{erw['gesamt'] / zwei['gesamt'] - 1:+.0%}")
+ zeichne(zwei, erw)
diff --git a/bilder_04/kap_supplychain_wirkung.png b/bilder_04/kap_supplychain_wirkung.png
new file mode 100644
index 0000000..91c255f
Binary files /dev/null and b/bilder_04/kap_supplychain_wirkung.png differ
diff --git a/bilder_04/kap_supplychain_wirkung.svg b/bilder_04/kap_supplychain_wirkung.svg
new file mode 100644
index 0000000..628d95b
--- /dev/null
+++ b/bilder_04/kap_supplychain_wirkung.svg
@@ -0,0 +1,2093 @@
+
+
+