diff --git a/CLAUDE.md b/CLAUDE.md
index 53e58db..ca8189c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -39,6 +39,8 @@ pyproject.toml Quelle: Abhängigkeiten in Gruppe
Dockerfile, .dockerignore Quelle: Kurs-Image (Programme + JupyterLab)
LICENSE, LICENSE-TEXT.md MIT fuer Code, CC BY-SA 4.0 fuer den Text
.gitattributes LF im Repository, egal auf welchem System
+.env.beispiel Vorlage; die echte .env (Ziel des Uploads)
+ ist bewusst NICHT im Repository
pandoc-defaults-basis.yaml, pandoc/, pandoc-defaults-buch.yaml PDF-Konfiguration
```
@@ -60,8 +62,17 @@ cd Version_04
python3 Operations_Research_mit_Python_Version_04/build_version_04.py --check # nur prüfen
python3 Operations_Research_mit_Python_Version_04/build_version_04.py --pdf --html
python3 Operations_Research_mit_Python_Version_04/extract_programme_04.py
+python3 Operations_Research_mit_Python_Version_04/veroeffentliche_04.py # hochladen
```
+`veroeffentliche_04.py` lädt `OR_HTML_04/` per `rsync --delete` nach
+— **nicht** per `scp`, denn das überschreibt nur und löscht nie; eine aus dem Buch entfernte
+Seite bliebe sonst dauerhaft online. Vorher prüft es, ob eine Quelldatei neuer ist als die
+gebaute Website (die Website hing einmal wochenlang einen Bau zurück, ohne dass es auffiel),
+hinterher ruft es vierzehn Adressen per HTTP ab. Bei mehr als 30 Löschungen bricht es ab —
+die Liste ist dann anzusehen, bevor `--loeschgrenze N` sie freigibt. Ziel in `.env`
+(nicht im Repository, Vorlage `.env.beispiel`); Anmeldung per SSH-Schlüssel, kein Passwort.
+
`--check` prüft mehr als die Struktur: fehlende Codezäune, Links auf nicht existierende
Dateien und **harte Kapitel-/Abschnitts-/Aufgabennummern im Quelltext**. Es meldet Datei und
Zeile. Der Suchausdruck erlaubt beliebigen Zwischenraum — der letzte gefundene Fall war ein
@@ -76,9 +87,10 @@ sonst fälschlich als sichtbaren Umbruch dar.
## Die wichtigsten Konventionen
**Keine abgeleiteten Zahlen im Quelltext.** Das ist die Regel, an der dieses Projekt am
-häufigsten gescheitert ist — **sieben** Mal an verschiedenen Stellen verletzt gefunden
+häufigsten gescheitert ist — **neun** Mal an verschiedenen Stellen verletzt gefunden
(Anhang A, 66 Programm-Docstrings, die Übersichtstabellen der Anhänge, `CLAUDE.md` selbst,
-eine Tabellenzelle, die 98 Lösungsmarken des Anhangs, neun Denkfehler-Verweise).
+eine Tabellenzelle, die 98 Lösungsmarken des Anhangs, neun Denkfehler-Verweise, sieben
+„Übung N.N"-Verweise in alter Zählung, zuletzt die Dateiübersicht des Leser-Wegweisers).
Seit dem letzten Fund trägt **keine** Marke mehr eine handgeschriebene Nummer, und `--check`
bewacht jede Familie. Konkret:
@@ -95,6 +107,11 @@ bewacht jede Familie. Konkret:
einer bereits nummerierten Überschrift. Verwiesen wird auf
`{ref:sec:-denkfehler}`.
* Im Fließtext `{ref:}`, nie eine Literalzahl.
+* **Die Dateiübersicht in `Operations_Research_mit_Python_Version_04/README.md`** steht
+ zwischen `` und `` und wird von
+ `schreibe_kapiteltabelle()` aus derselben Struktur erzeugt wie die Nummern im Buch. Nicht
+ von Hand bearbeiten — `--check` meldet einen veralteten Block, ein Lauf ohne `--check`
+ zieht ihn nach.
* In Code-Kommentaren und Docstrings der Kapitel**name** („Kapitel Metaheuristiken:"), nie
die Nummer. Auch nicht im Dateinamen.
@@ -137,8 +154,10 @@ gehört wie die Arbeit, die sie beschreibt.
## Diagramme
-`bilder_04/erzeuge_*.py` erzeugen 18 der 32 SVGs, jeweils **aus derselben Instanz wie das
-zugehörige Buchprogramm**. Das ist kein Selbstzweck: Von zehn nachgebauten Bildern förderten
+`bilder_04/erzeuge_*.py` erzeugen 18 der 32 Kapiteldiagramme, jeweils **aus derselben
+Instanz wie das zugehörige Buchprogramm**. (Das 33. SVG im Verzeichnis ist `titelseite.svg`,
+die der Build bei jedem Lauf selbst schreibt — deshalb nennt `README.md` 33 und diese Datei
+32.) Das ist kein Selbstzweck: Von zehn nachgebauten Bildern förderten
sieben einen Fehler zutage — dreimal ein Modell, das im Buch gar nicht vorkommt, einmal
widersprüchliche Zahlen zwischen Bild und Text, einmal ein gekipptes Vorzeichen. Wer ein
Diagramm anfasst, vergleicht es zuerst mit dem Modell des Kapitels.
@@ -174,10 +193,14 @@ Die Website hat vier Übersichtsseiten: `index.html` (Landing), `programme.html`
weiterleitet — sonst antwortet der Webserver mit 403, weil Directory-Listing abgeschaltet
ist.
-**Nach jedem `--pdf --html` muss `OR_HTML_04/` neu auf den Webserver kopiert werden** — sonst
-laeuft die veroeffentlichte Fassung dem Repository hinterher. Woran man das merkt: Ein
-`curl -s https://jamulix.de/OR/lp.html | grep -c colab.research` muss **0** ergeben, und die
-Groesse des dort liegenden PDF muss zur lokalen Datei passen.
+**Nach jedem `--pdf --html` gehoert `veroeffentliche_04.py` hinterher** — sonst laeuft die
+veroeffentlichte Fassung dem Repository hinterher, und genau das ist einmal wochenlang
+unbemerkt geblieben. Von Hand muss dabei nichts mehr geprueft werden: Das Skript weigert
+sich, einen veralteten Bau hochzuladen, und ruft anschliessend vierzehn Adressen ab.
+
+Der erste Lauf am 08.09.2026 uebertrug 213 Dateien und loeschte 35 — 33 verwaiste PNGs, ein
+leeres `__pycache__/` und die geteilte Anhangseite. Alle 35 haette ein `scp`-Upload stehen
+gelassen.
**`COLAB_BASIS_URL` ist bewusst leer.** Colab oeffnet Notebooks nur aus GitHub, Google Drive
oder einem Upload; die URL-Form `colab.research.google.com/github/...` ist fest auf GitHub
diff --git a/Operations_Research_mit_Python_Version_04/README.md b/Operations_Research_mit_Python_Version_04/README.md
index 644da5a..290c026 100644
--- a/Operations_Research_mit_Python_Version_04/README.md
+++ b/Operations_Research_mit_Python_Version_04/README.md
@@ -29,63 +29,81 @@ arbeiten, üben und eigene Anwendungen bauen können.**
Der Text ist auf mehrere Dateien verteilt, damit er handhabbar bleibt. Die Nummern-Präfixe
legen die Lesereihenfolge fest.
+
+
### Vorspann
| Datei | Inhalt |
| --- | --- |
-| [`00_Vorwort_und_Lesehilfe.md`](00_Vorwort_und_Lesehilfe.md) | Vorwort, Lernpfade, Verzeichnis der Beispielprogramme, Setup, wie die Lernelemente funktionieren |
-| [`01_Notation_und_Abkuerzungen.md`](01_Notation_und_Abkuerzungen.md) | Alle Symbole und Abkürzungen, ausgeschrieben und erklärt |
+| [`00_Vorwort_und_Lesehilfe.md`](00_Vorwort_und_Lesehilfe.md) | Vorwort & Lesehilfe |
+| [`01_Notation_und_Abkuerzungen.md`](01_Notation_und_Abkuerzungen.md) | Notation & Abkürzungen |
-### Teil I — Grundlagen
+### Teil I: Grundlagen des Operations Research
| Datei | Kapitel |
| --- | --- |
-| [`10_Einfuehrung_OR.md`](10_Einfuehrung_OR.md) | 1 — Einführung in Operations Research |
-| [`11_Mathematisches_Fundament.md`](11_Mathematisches_Fundament.md) | 2 — Vektoren, Matrizen, Konvexität |
-| [`12_Python_Oekosystem.md`](12_Python_Oekosystem.md) | 3 — Das Python-Ökosystem für OR |
+| [`10_Einfuehrung_OR.md`](10_Einfuehrung_OR.md) | Kapitel 1 — Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung |
+| [`11_Mathematisches_Fundament.md`](11_Mathematisches_Fundament.md) | Kapitel 2 — Das mathematische Fundament — Vektoren, Matrizen, Konvexität |
+| [`12_Python_Oekosystem.md`](12_Python_Oekosystem.md) | Kapitel 3 — Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten |
+| [`13_Vom_Wunsch_zum_Modell.md`](13_Vom_Wunsch_zum_Modell.md) | Kapitel 4 — Vom Management-Wunsch zum Modell |
+| [`19_Synthese_Grundlagen.md`](19_Synthese_Grundlagen.md) | Synthese Teil I |
-### Teil II — Deterministische Kernverfahren
+### Teil II: Die Kernverfahren der deterministischen Optimierung
| Datei | Kapitel |
| --- | --- |
-| [`20_Lineare_Programmierung.md`](20_Lineare_Programmierung.md) | 4 — Lineare Programmierung, Simplex, Dualität |
-| [`21_MILP.md`](21_MILP.md) | 5 — Gemischt-ganzzahlige Optimierung |
-| [`22_CP_SAT.md`](22_CP_SAT.md) | 6 — Constraint Programming mit CP-SAT |
-| [`23_Graphen_Fluesse_Touren.md`](23_Graphen_Fluesse_Touren.md) | 7 — Graphen, Flüsse, Tourenplanung |
+| [`20_Lineare_Programmierung.md`](20_Lineare_Programmierung.md) | Kapitel 5 — Lineare Programmierung — Simplex, Dualität und Schattenpreise |
+| [`21_MILP.md`](21_MILP.md) | Kapitel 6 — Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound |
+| [`22_CP_SAT.md`](22_CP_SAT.md) | Kapitel 7 — Constraint Programming mit CP-SAT — Logik, Scheduling und Zuweisung |
+| [`23_Graphen_Fluesse_Touren.md`](23_Graphen_Fluesse_Touren.md) | Kapitel 8 — Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP |
+| [`24_Metaheuristiken.md`](24_Metaheuristiken.md) | Kapitel 9 — Metaheuristiken — wenn der exakte Solver aussteigt |
+| [`25_Spaltengenerierung.md`](25_Spaltengenerierung.md) | Kapitel 10 — Spaltengenerierung — das Modell umbauen statt die Lösung raten |
+| [`29_Synthese_Kernverfahren.md`](29_Synthese_Kernverfahren.md) | Synthese Teil II |
-### Teil III — Nichtlinearität, Unsicherheit, Dynamik
+### Teil III: Nichtlinearität, Unsicherheit und mehrperiodige Dynamik
| Datei | Kapitel |
| --- | --- |
-| [`30_QP_und_NLP.md`](30_QP_und_NLP.md) | 8 — Quadratische und nichtlineare Optimierung, KKT |
-| [`31_Unsicherheit.md`](31_Unsicherheit.md) | 9 — Monte-Carlo, stochastische und robuste Optimierung |
-| [`32_Dynamische_Programmierung.md`](32_Dynamische_Programmierung.md) | 10 — Dynamische Programmierung, Bellman |
+| [`30_QP_und_NLP.md`](30_QP_und_NLP.md) | Kapitel 11 — Quadratische und nichtlineare Optimierung — KKT, Lagrange, Konvexität |
+| [`31_Unsicherheit.md`](31_Unsicherheit.md) | Kapitel 12 — Optimierung unter Unsicherheit — Monte-Carlo, Stochastik, Robustheit |
+| [`32_Dynamische_Programmierung.md`](32_Dynamische_Programmierung.md) | Kapitel 13 — Dynamische Programmierung — Die Bellman-Gleichung und Order-Execution |
+| [`33_Mehrziel_und_Pareto.md`](33_Mehrziel_und_Pareto.md) | Kapitel 14 — Mehrere Ziele — Pareto-Fronten statt Gewichte |
+| [`34_Predict_then_Optimize.md`](34_Predict_then_Optimize.md) | Kapitel 15 — Predict-then-Optimize — die bessere Prognose, die schlechtere Entscheidung |
+| [`39_Synthese_Nichtlinear.md`](39_Synthese_Nichtlinear.md) | Synthese Teil III |
-### Teil IV — Operations Research an den Finanzmärkten
+### Teil IV: Anwendungen — Energiewirtschaft und Finanzmärkte
| Datei | Kapitel |
| --- | --- |
-| [`40_Finanzdaten.md`](40_Finanzdaten.md) | 11 — Finanzdaten, Kovarianz, Shrinkage |
-| [`41_Markowitz.md`](41_Markowitz.md) | 12 — Markowitz-Portfoliotheorie |
-| [`42_CVaR.md`](42_CVaR.md) | 13 — Tail-Risiko, CVaR, Transaktionskosten |
-| [`43_Handelsmaschine.md`](43_Handelsmaschine.md) | 14 — Die vollständige Handelsmaschine |
+| [`35_Strukturbruecke.md`](35_Strukturbruecke.md) | Kapitel 16 — Die Strukturbrücke — dieselbe Mathematik, zwei Welten |
+| [`36_Supply_Chain_Energie.md`](36_Supply_Chain_Energie.md) | Kapitel 17 — Supply-Chain und Energieeinsatz unter Unsicherheit |
+| [`40_Finanzdaten.md`](40_Finanzdaten.md) | Kapitel 18 — Finanzdaten-Modellierung — Renditen, Kovarianz und Shrinkage |
+| [`41_Markowitz.md`](41_Markowitz.md) | Kapitel 19 — Die moderne Portfoliotheorie nach Markowitz |
+| [`42_CVaR.md`](42_CVaR.md) | Kapitel 20 — Tail-Risiko, CVaR und Transaktionskosten |
+| [`43_Handelsmaschine.md`](43_Handelsmaschine.md) | Kapitel 21 — Die vollständige quantitative Handelsmaschine |
+| [`49_Synthese_Anwendungen.md`](49_Synthese_Anwendungen.md) | Synthese Teil IV |
-### Teil V — Praxis
+### Teil V: Praxis
| Datei | Kapitel |
| --- | --- |
-| [`50_Praxis.md`](50_Praxis.md) | 15 — Praxisfallen und produktiver Einsatz |
-| [`59_Projektwerkstatt.md`](59_Projektwerkstatt.md) | Projektwerkstatt: 8 eigene Anwendungen |
+| [`50_Praxis.md`](50_Praxis.md) | Kapitel 22 — Praxisfallen und der Weg zum produktiven Einsatz |
+| [`51_Testing_und_Deployment.md`](51_Testing_und_Deployment.md) | Kapitel 23 — Testen, Messen, Ausliefern |
+| [`52_Synthese_Praxis.md`](52_Synthese_Praxis.md) | Synthese Teil V |
+| [`59_Projektwerkstatt.md`](59_Projektwerkstatt.md) | Projektwerkstatt |
### Anhang
| Datei | Inhalt |
| --- | --- |
-| [`90_Anhang_Loesungen.md`](90_Anhang_Loesungen.md) | Lösungen zu **allen** Übungsaufgaben und Selbsttests |
-| [`91_Anhang_Modellierungsmuster.md`](91_Anhang_Modellierungsmuster.md) | Musterkatalog: 24 wiederkehrende Modellierungstricks |
-| [`92_Anhang_Fehlerdiagnose.md`](92_Anhang_Fehlerdiagnose.md) | Diagnose-Handbuch für Solver-Probleme |
-| [`94_Anhang_Glossar.md`](94_Anhang_Glossar.md) | Glossar der Fachbegriffe |
-| [`95_Anhang_Literatur.md`](95_Anhang_Literatur.md) | Literaturverzeichnis |
+| [`90_Anhang_Loesungen.md`](90_Anhang_Loesungen.md) | Anhang A — Lösungen zu allen Übungsaufgaben |
+| [`91_Anhang_Modellierungsmuster.md`](91_Anhang_Modellierungsmuster.md) | Anhang B — Katalog der Modellierungsmuster |
+| [`92_Anhang_Fehlerdiagnose.md`](92_Anhang_Fehlerdiagnose.md) | Anhang C — Fehlerdiagnose-Handbuch |
+| [`93_Anhang_Spickzettel.md`](93_Anhang_Spickzettel.md) | Anhang D — Spickzettel der Solver |
+| [`94_Anhang_Glossar.md`](94_Anhang_Glossar.md) | Anhang E — Glossar |
+| [`95_Anhang_Literatur.md`](95_Anhang_Literatur.md) | Anhang F — Literaturverzeichnis |
+
+
---
@@ -95,7 +113,7 @@ Wer lieber ein einziges Dokument möchte (z. B. zum Drucken oder für die PDF-Er
baut es aus den Einzeldateien:
```bash
-cd OR_mit_Python/Version_04 # Wurzel der Version 04
+cd Version_04 # das Wurzelverzeichnis des Repositorys
python3 Operations_Research_mit_Python_Version_04/build_version_04.py
```
@@ -105,11 +123,15 @@ Inhaltsverzeichnis-Platzhalter und entfernten unnötigen Zeilenumbrüchen innerh
Absätzen. Optionen:
```bash
-python3 .../build_version_03.py --pdf # zusätzlich PDF über pandoc + xelatex
-python3 .../build_version_03.py --html # zusätzlich Single-Page-HTML
-python3 .../build_version_03.py --check # nur Konsistenzprüfung, schreibt nichts
+python3 .../build_version_04.py --pdf # zusätzlich PDF über pandoc + xelatex
+python3 .../build_version_04.py --html # zusätzlich die Website OR_HTML_04/
+python3 .../build_version_04.py --check # nur Konsistenzprüfung, schreibt nichts
```
+`--html` erzeugt die **mehrseitige** Website mit Suche, Stichwortverzeichnis und
+Notebook-Downloads; das Gesamtdokument auf einer Seite liegt darin als
+`gesamtdokument.html`.
+
---
## Empfohlene Reihenfolge für Kursteilnehmer
diff --git a/Operations_Research_mit_Python_Version_04/build_version_04.py b/Operations_Research_mit_Python_Version_04/build_version_04.py
index 9150294..eadbd6c 100644
--- a/Operations_Research_mit_Python_Version_04/build_version_04.py
+++ b/Operations_Research_mit_Python_Version_04/build_version_04.py
@@ -2443,6 +2443,86 @@ def baue_stichwortverzeichnis(html_verz: str, struktur: list[dict],
print(f"Stichwortverzeichnis erzeugt: {len(namen)} Hauptbegriffe.")
+# Der Leser-Wegweiser Operations_Research_mit_Python_Version_04/README.md fuehrt
+# alle Quelldateien mit ihrer Kapitelnummer auf. Diese Tabelle stand von Hand
+# darin - und war der NEUNTE Fall der Regel "keine abgeleiteten Zahlen im
+# Quelltext": 12 der 15 Nummern zeigten noch die Zaehlung der Version 03, und
+# 8 der 23 Kapitel fehlten ganz. Jetzt erzeugt der Bau den Block zwischen den
+# beiden Marken; --check meldet, wenn er veraltet ist.
+README_MARKE_ANFANG = ""
+README_MARKE_ENDE = ""
+
+LESER_README = os.path.join(HIER, "README.md")
+
+
+def baue_kapiteltabelle(struktur: list[dict]) -> str:
+ """Baut die Dateiuebersicht des Leser-Wegweisers aus der Struktur, die
+ baue_seiten_registry() ohnehin ermittelt - dieselbe Quelle, aus der auch
+ die Nummern im Buch stammen. Damit koennen die beiden nicht auseinander-
+ laufen."""
+ zeilen = [README_MARKE_ANFANG, ""]
+
+ def tabelle(eintraege: list[dict], spalte: str) -> None:
+ zeilen.append(f"| Datei | {spalte} |")
+ zeilen.append("| --- | --- |")
+ for e in eintraege:
+ was = f'{e["nummer"]} — {e["titel"]}' if e["nummer"] else e["titel"]
+ zeilen.append(f'| [`{e["datei"]}`]({e["datei"]}) | {was} |')
+ zeilen.append("")
+
+ vorspann = [e for e in struktur if e["teil"] is None and e["typ"] == "sonder"]
+ if vorspann:
+ zeilen.append("### Vorspann")
+ zeilen.append("")
+ tabelle(vorspann, "Inhalt")
+
+ gesehen: list[str] = []
+ for e in struktur:
+ if e["teil"] and e["teil"] not in gesehen:
+ gesehen.append(e["teil"])
+ for teil in gesehen:
+ zeilen.append(f"### {teil}")
+ zeilen.append("")
+ tabelle([e for e in struktur if e["teil"] == teil], "Kapitel")
+
+ anhaenge = [e for e in struktur if e["typ"] == "anhang"]
+ if anhaenge:
+ zeilen.append("### Anhang")
+ zeilen.append("")
+ tabelle(anhaenge, "Inhalt")
+
+ zeilen.append(README_MARKE_ENDE)
+ return "\n".join(zeilen)
+
+
+def schreibe_kapiteltabelle(struktur: list[dict], nur_pruefen: bool) -> list[str]:
+ """Ersetzt den markierten Block im Leser-Wegweiser. Liefert bei --check
+ eine nicht leere Liste, wenn er veraltet ist."""
+ if not os.path.exists(LESER_README):
+ return []
+ with open(LESER_README, encoding="utf-8") as f:
+ inhalt = f.read()
+ if README_MARKE_ANFANG not in inhalt or README_MARKE_ENDE not in inhalt:
+ print(f"FEHLER: {os.path.basename(LESER_README)} hat keine "
+ f"KAPITELTABELLE-Marken - die Dateiuebersicht kann nicht "
+ f"erzeugt werden.")
+ return ["README-Marken"]
+ vorher, rest = inhalt.split(README_MARKE_ANFANG, 1)
+ _, nachher = rest.split(README_MARKE_ENDE, 1)
+ neu = vorher + baue_kapiteltabelle(struktur) + nachher
+ if neu == inhalt:
+ return []
+ if nur_pruefen:
+ print(f"FEHLER: Die Dateiuebersicht in "
+ f"{os.path.basename(LESER_README)} ist veraltet - ein Lauf ohne "
+ f"--check zieht sie nach.")
+ return ["README-Kapiteltabelle"]
+ with open(LESER_README, "w", encoding="utf-8") as f:
+ f.write(neu)
+ print(f"Dateiuebersicht im Leser-Wegweiser nachgezogen.")
+ return []
+
+
def erzeuge_suchindex(html_verz: str, suchindex: list[dict]) -> None:
# .js mit globaler Variable statt .json + fetch() - siehe Kommentar in
# baue_programme_seite() (fetch() auf lokale Dateien scheitert unter
@@ -2511,6 +2591,13 @@ def main() -> int:
print(f"Geprueft: {len(DATEIEN)} Dateien, {zeilen:,} Zeilen, "
f"{zeichen/1024:.0f} KB, {kapitel} Hauptueberschriften.")
+ # Die Dateiuebersicht des Leser-Wegweisers stammt aus derselben Struktur
+ # wie die Nummern im Buch. Bei --check wird nur gemeldet, dass sie veraltet
+ # ist; geschrieben wird sie im normalen Lauf.
+ _, _, _, struktur_fuer_readme, _ = baue_seiten_registry()
+ if schreibe_kapiteltabelle(struktur_fuer_readme, nur_pruefen=argumente.check):
+ return 1
+
if argumente.check:
print("Nur Pruefung - es wurde nichts geschrieben.")
return 0
diff --git a/Operations_Research_mit_Python_Version_04/veroeffentliche_04.py b/Operations_Research_mit_Python_Version_04/veroeffentliche_04.py
index bd03e92..1901de9 100755
--- a/Operations_Research_mit_Python_Version_04/veroeffentliche_04.py
+++ b/Operations_Research_mit_Python_Version_04/veroeffentliche_04.py
@@ -192,7 +192,13 @@ def rsync_befehl(env: dict[str, str], probelauf: bool) -> list[str]:
# ohnehin nicht setzen, -a erzeugte dafuer nur Warnungen. --chmod sorgt
# dafuer, dass der Webserver alles lesen kann, unabhaengig von der lokalen
# umask.
- befehl = ["rsync", "-rltz", "--delete", "--chmod=D755,F644",
+ # --checksum: vergleiche INHALTE, nicht Groesse und Zeitstempel. Der Bau
+ # schreibt alle 42 Seiten bei jedem Lauf neu, auch wenn sich nichts
+ # geaendert hat - nach der Vorgabe waeren das jedes Mal rund 19 MB
+ # sinnlose Uebertragung. Mit --checksum meldet der Probelauf genau die
+ # Dateien, deren Inhalt sich wirklich unterscheidet. Das Lesen der 23 MB
+ # auf beiden Seiten faellt dagegen nicht ins Gewicht.
+ befehl = ["rsync", "-rltz", "--checksum", "--delete", "--chmod=D755,F644",
"--skip-compress=pdf/zip/woff2/png/jpg/svg",
"--itemize-changes",
"-e", ssh_optionen(env)]
diff --git a/PROGRESS.md b/PROGRESS.md
index 3ae92a2..5ce8312 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -2158,6 +2158,38 @@ Damit ist der seit dem Colab-Umbau offene Punkt erledigt: Online steht kein tote
Colab-Link mehr, `Notebooks_04/` liefert statt 403 eine Übersichtsseite, und das PDF
entspricht byte-genau dem gebauten.
+### 🐛 Neunter Fund: die Dateiübersicht des Leser-Wegweisers
+
+Beim Durchgehen der Dokumentation zeigte sich, dass
+`Operations_Research_mit_Python_Version_04/README.md` — die Datei, die **Leser** als erstes
+öffnen — noch die Struktur der Version 03 beschrieb:
+
+* **12 der 15 Kapitelnummern waren falsch.** „20_Lineare_Programmierung.md | 4" — es ist
+ Kapitel 5. Bei den Finanzkapiteln lag die Abweichung bei sieben: README 11, tatsächlich 18.
+* **8 der 23 Kapitel fehlten ganz** (unter anderem Metaheuristiken, Spaltengenerierung,
+ Mehrziel, Predict-then-Optimize, Testing).
+* Auch `93_Anhang_Spickzettel.md` fehlte in der Anhangtabelle, und der Bauabschnitt nannte
+ dreimal `build_version_03.py` statt `_04`, dazu „Single-Page-HTML" für einen Schalter, der
+ längst eine mehrseitige Website erzeugt.
+
+Dieselbe Regel, dieselbe Ursache: eine abgeleitete Zahl von Hand geschrieben. Deshalb dieselbe
+Behandlung — **die Tabelle wird jetzt erzeugt**, nicht korrigiert. `schreibe_kapiteltabelle()`
+ersetzt den Block zwischen `` und ``
+aus der Struktur, die `baue_seiten_registry()` ohnehin ermittelt — derselben Quelle, aus der
+die Nummern im Buch stammen. Auseinanderlaufen können die beiden damit nicht mehr.
+
+`--check` meldet einen veralteten Block und bricht ab; ein Lauf ohne `--check` zieht ihn nach.
+Gegenprobe: Alle **37** Dateien aus `DATEIEN` stehen in der erzeugten Übersicht, mit 23
+Kapiteln, 5 Teil-Synthesen und 6 Anhängen.
+
+**Das Muster ist inzwischen belastbar:** Neun Funde, neun Prüfungen. Keiner davon wurde durch
+Nachdenken gefunden — jeder durch einen Vergleich der Behauptung mit der Messung.
+
+**Nebenbei am Upload-Skript:** `rsync` bekommt jetzt `--checksum`. Der Bau schreibt alle 42
+Seiten bei jedem Lauf neu, auch wenn sich nichts geändert hat; nach Zeitstempel und Größe
+hätte das jedes Mal rund 19 MB sinnlose Übertragung bedeutet. Gemessen nach einem Neubau
+ohne inhaltliche Änderung: **0 statt 151 Dateien**.
+
---
## 8. Commit-Historie des V04-Strangs
diff --git a/README.md b/README.md
index 5f7f4f1..eaa8ea2 100644
--- a/README.md
+++ b/README.md
@@ -155,7 +155,7 @@ python3 bilder_04/erzeuge_kondition.py
`--check` prüft mehr als die Struktur: fehlende Codezäune, tote Dateilinks, eine gebrochene
Lesekette, Lösungsabschnitte mit falscher Aufgabenzahl — und **jede von Hand geschriebene
Nummer**, die der Build selbst vergeben müsste. Diese Regel ist die in diesem Projekt am
-häufigsten verletzte; acht Fälle wurden nachträglich gefunden, und jeder hat eine eigene
+häufigsten verletzte; neun Fälle wurden nachträglich gefunden, und jeder hat eine eigene
Prüfung hinterlassen.
### Externe Werkzeuge