Titeltext: eine Quelle fuer PDF-Titelseite und Startseite

Die Startseite zeigte einen anderen Titeltext als das PDF. Der Text stand
VIERMAL im Repository: NEUER_TITEL.md, die Konstanten in
bilder_04/erzeuge_titelseite.py, der Titelblock des Vorworts - und noch einmal
fest einprogrammiert im Hero-Block von baue_landingpage().

Die Ursache ist messbar: Die drei Commits, die den Titel ueberarbeitet haben
(ca5621f, 199dd3b, 1659fb9 im Ursprungs-Repository), fassten jedes Mal
dieselben drei Dateien an. build_version_04.py war in keinem davon, und
OR_HTML_04/index.html taucht in ihren Dateilisten gar nicht auf. Online stand
deshalb:

  Unterzeile  "Von den mathematischen Grundlagen zur quantitativen
              Portfolio-Optimierung an den Finanzmaerkten."
  Kompendium  "Ein vollstaendiges Kompendium mit Algorithmen ..."
  Anwendungen fehlte ganz

Behandlung wie bei den neun Nummern-Funden zuvor: nicht die Kopie korrigieren,
sondern die Kopie abschaffen. Das neue titeltexte_04.py zerlegt NEUER_TITEL.md
in Titel, Untertitel, Kompendium- und Anwendungen-Absatz; PDF-Titelseite und
Startseite lesen dieselbe Funktion. Der Autor bleibt bewusst Konstante im
Generator - die Vorlage beschreibt das Buch, nicht sein Impressum.

Der entscheidende Beleg: Die neu erzeugte titelseite.svg ist BYTE-IDENTISCH
(97fb40d7...) zur vorherigen - der Umbau hat am PDF nachweislich nichts
veraendert. Auch nicht am Umbruch: Die frueher handgesetzten drei Zeilen
entstehen jetzt automatisch aus _umbrechen(), und zwar exakt so. Ein
Textvergleich ueber das SVG war uebrigens nicht moeglich - es enthaelt kein
einziges <text>-Element, matplotlib zeichnet alle 726 Buchstaben als Kurven.

Das Vorwort bleibt von Hand gepflegter Buchtext, wird aber von
pruefe_titeltexte() bei jedem --check gegen die Quelle gestellt. Gleich beim
ersten Lauf fand es einen echten Unterschied: Das Vorwort setzte einen
Geviertstrich, die Vorlage einen Halbgeviertstrich. Im Deutschen ist der
Halbgeviertstrich der richtige - das Vorwort folgt jetzt der Quelle.

Veroeffentlicht: 5 Dateien uebertragen, alle vierzehn HTTP-Proben richtig.
Gegenprobe vom Server: alle drei Absaetze der Startseite sind zeichengenau
identisch mit NEUER_TITEL.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dschlueter 2026-09-08 19:39:49 +02:00
commit 022bd4eaca
15 changed files with 250 additions and 37 deletions

View file

@ -34,7 +34,8 @@ OR_HTML_04/ generiert: Mehrseiten-Website
Operations_Research_mit_Python_Version_04_Programme/ generiert: Beispielprogramme
Notebooks_04/ generiert: ein .ipynb je Kapitel
Kritik_und_Verbesserungsvorschlaege/ Quelle: Rezension, Verbesserungsvorschläge,
NEUER_TITEL.md (Vorlage des Titelblatts)
NEUER_TITEL.md - die EINE Quelle des
Titeltexts (PDF-Titelseite + Startseite)
pyproject.toml Quelle: Abhängigkeiten in Gruppen
Dockerfile, .dockerignore Quelle: Kurs-Image (Programme + JupyterLab)
LICENSE, LICENSE-TEXT.md MIT fuer Code, CC BY-SA 4.0 fuer den Text
@ -87,10 +88,11 @@ 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 — **neun** Mal an verschiedenen Stellen verletzt gefunden
häufigsten gescheitert ist — **zehn** 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, sieben
„Übung N.N"-Verweise in alter Zählung, zuletzt die Dateiübersicht des Leser-Wegweisers).
„Übung N.N"-Verweise in alter Zählung, die Dateiübersicht des Leser-Wegweisers,
zuletzt der Titeltext der Startseite).
Seit dem letzten Fund trägt **keine** Marke mehr eine handgeschriebene Nummer, und `--check`
bewacht jede Familie. Konkret:
@ -107,6 +109,12 @@ bewacht jede Familie. Konkret:
einer bereits nummerierten Überschrift. Verwiesen wird auf
`{ref:sec:<kapitel>-denkfehler}`.
* Im Fließtext `{ref:<label>}`, nie eine Literalzahl.
* **Der Titeltext steht in `Kritik_und_Verbesserungsvorschlaege/NEUER_TITEL.md`** und
nirgends sonst. `titeltexte_04.py` liest ihn; die PDF-Titelseite
(`bilder_04/erzeuge_titelseite.py`) und der Hero-Block der Startseite
(`baue_landingpage()`) speisen sich daraus. Eine Titeländerung wird **einmal** dort
gemacht. Das Vorwort trägt denselben Text als Buchprosa — `pruefe_titeltexte()`
vergleicht ihn bei jedem `--check` mit der Quelle.
* **Die Dateiübersicht in `Operations_Research_mit_Python_Version_04/README.md`** steht
zwischen `<!-- KAPITELTABELLE:ANFANG -->` und `<!-- KAPITELTABELLE:ENDE -->` und wird von
`schreibe_kapiteltabelle()` aus derselben Struktur erzeugt wie die Nummern im Buch. Nicht

View file

@ -309,6 +309,7 @@ article li { margin: .3em 0; }
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
.hero-badges { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
.hero-kompendium { color: var(--muted); margin-top: 1.4rem; max-width: 640px; }
.hero-anwendungen { color: var(--muted); margin-top: .8rem; max-width: 640px; }
.hero-autor { color: var(--muted); font-size: .92rem; line-height: 1.5; }
.badge { padding: .35em .9em; border-radius: 20px; color: #fff; font-weight: 600; font-size: .85rem; }
.badge-indigo { background: var(--accent); }

View file

@ -378,7 +378,7 @@
<h1 class="unnumbered" id="optimierte-entscheidungsfindung-mit-python">Optimierte Entscheidungsfindung mit Python</h1>
<h2 class="unnumbered" id="von-der-mathematischen-modellierung-zu-praktischen-intelligenten-lösungen-für-betrieb-technik-und-finanzmärkte">Von der mathematischen Modellierung zu praktischen intelligenten Lösungen für Betrieb, Technik und Finanzmärkte</h2>
<p><strong>Ein praxisorientiertes Kompendium für Entscheider</strong> mit Constraint Programming, Vektor- und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.</p>
<p>Von <strong>Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung</strong> über <strong>Energieverteilung und Ressourcenplanung</strong> bis zur <strong>Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten</strong> mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.</p>
<p>Von <strong>Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung</strong> über <strong>Energieverteilung und Ressourcenplanung</strong> bis zur <strong>Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten</strong> mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.</p>
<p>Autor / Herausgeber: Dieter Schlüter</p>
<p>&lt;dieter(dot)schlueter(atsign)linix(dot)de&gt;</p>
<p>Stand: 8. September 2026</p>

8
OR_HTML_04/index.html generated
View file

@ -104,17 +104,15 @@
<div class="hero-text">
<p class="hero-kicker">KOMPENDIUM</p>
<h1>Optimierte Entscheidungsfindung mit Python</h1>
<p class="hero-untertitel">Von den mathematischen Grundlagen zur quantitativen
Portfolio-Optimierung an den Finanzmärkten.</p>
<p class="hero-untertitel">Von der mathematischen Modellierung zu praktischen intelligenten Lösungen für Betrieb, Technik und Finanzmärkte</p>
<div class="hero-badges">
<span class="badge badge-indigo">HiGHS</span>
<span class="badge badge-emerald">SciPy</span>
<span class="badge badge-cyan">Google OR-Tools</span>
<span class="badge badge-violett">CVXPY</span>
</div>
<p class="hero-kompendium"><em>Ein vollständiges Kompendium mit Algorithmen, Constraint
Programming, Vektor- und Matrixmethoden sowie modernen Python-Solvern (HiGHS, SciPy,
Google OR-Tools, CVXPY)</em></p>
<p class="hero-kompendium"><em>Ein praxisorientiertes Kompendium für Entscheider mit Constraint Programming, Vektor- und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.</em></p>
<p class="hero-anwendungen">Von Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung über Energieverteilung und Ressourcenplanung bis zur Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.</p>
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
&lt;dieter(dot)schlueter(atsign)linix(dot)de&gt;<br>
Stand: 8. September 2026</p>

View file

@ -107,7 +107,7 @@
<h1 class="unnumbered" id="optimierte-entscheidungsfindung-mit-python">Optimierte Entscheidungsfindung mit Python</h1>
<h2 class="unnumbered" id="von-der-mathematischen-modellierung-zu-praktischen-intelligenten-lösungen-für-betrieb-technik-und-finanzmärkte">Von der mathematischen Modellierung zu praktischen intelligenten Lösungen für Betrieb, Technik und Finanzmärkte</h2>
<p><strong>Ein praxisorientiertes Kompendium für Entscheider</strong> mit Constraint Programming, Vektor- und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.</p>
<p>Von <strong>Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung</strong> über <strong>Energieverteilung und Ressourcenplanung</strong> bis zur <strong>Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten</strong> mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.</p>
<p>Von <strong>Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung</strong> über <strong>Energieverteilung und Ressourcenplanung</strong> bis zur <strong>Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten</strong> mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.</p>
<p>Autor / Herausgeber: Dieter Schlüter</p>
<p>&lt;dieter(dot)schlueter(atsign)linix(dot)de&gt;</p>
<p>Stand: 8. September 2026</p>

View file

@ -20,7 +20,7 @@
**Ein praxisorientiertes Kompendium für Entscheider** mit Constraint Programming, Vektor- und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.
Von **Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung** über **Energieverteilung und Ressourcenplanung** bis zur **Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten** mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.
Von **Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung** über **Energieverteilung und Ressourcenplanung** bis zur **Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten** mit Python modellieren, optimieren und belegbar fundierte Entscheidungen treffen.
Autor / Herausgeber: Dieter Schlüter

View file

@ -23,7 +23,7 @@ und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.
Von **Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und
Maschinen- und Netzwerkauslastung** über **Energieverteilung und Ressourcenplanung** bis zur
**Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten** mit Python
**Portfoliooptimierung und besseren Entscheidungen an den Finanzmärkten** mit Python
modellieren, optimieren und belegbar fundierte Entscheidungen treffen.
Autor / Herausgeber: Dieter Schlüter

View file

@ -37,6 +37,8 @@ import subprocess
import sys
import zipfile
from titeltexte_04 import lies_titeltexte, _saeubern as _saeubere_titeltext
HIER = os.path.dirname(os.path.abspath(__file__))
BASIS = os.path.dirname(HIER)
@ -389,9 +391,77 @@ def pruefe_dateien() -> list[str]:
fehlend.append(name)
fehlend.extend(pruefe_assets())
fehlend.extend(pruefe_titeltexte())
return fehlend
# Die Datei, die den Titeltext ein viertes Mal traegt: als Buchtext, den ein
# Leser im Vorwort sieht. Erzeugt wird er dort NICHT - er steht mit
# Hervorhebungen im Fliesstext und gehoert dem Autor. Aber er wird geprueft.
VORWORT_DATEI = "00_Vorwort_und_Lesehilfe.md"
def pruefe_titeltexte() -> list[str]:
"""Vergleicht den Titelblock des Vorworts mit NEUER_TITEL.md.
PDF-Titelseite und Startseite lesen die Quelle direkt und koennen deshalb
gar nicht abweichen. Das Vorwort ist die dritte Stelle - und genau eine
solche ungeprueft mitgefuehrte Kopie hat dazu gefuehrt, dass die Startseite
monatelang einen anderen Untertitel zeigte als das PDF."""
quelle = lies_titeltexte()
pfad = os.path.join(HIER, VORWORT_DATEI)
if not os.path.exists(pfad):
return []
with open(pfad, encoding="utf-8") as f:
zeilen = f.read().split("\n")
# Gesucht werden die '#'- und '##'-Ueberschrift und die beiden Absaetze
# danach. Der '{-}'-Zusatz (Pandoc: nicht nummerieren) gehoert nicht zum
# Text.
def ohne_marke(text: str) -> str:
return re.sub(r"\s*\{-\}\s*$", "", text)
gefunden: dict[str, tuple[int, str]] = {}
absaetze: list[tuple[int, str]] = []
block: list[str] = []
block_start = 0
for nr, zeile in enumerate(zeilen, start=1):
if zeile.startswith("# ") and "titel" not in gefunden:
gefunden["titel"] = (nr, _saeubere_titeltext(ohne_marke(zeile[2:])))
elif zeile.startswith("## ") and "untertitel" not in gefunden:
gefunden["untertitel"] = (nr, _saeubere_titeltext(ohne_marke(zeile[3:])))
elif zeile.strip() and not zeile.startswith("#") and "untertitel" in gefunden:
if not block:
block_start = nr
block.append(zeile)
else:
if block:
absaetze.append((block_start, _saeubere_titeltext(" ".join(block))))
block = []
if len(absaetze) == 2:
break
if block:
absaetze.append((block_start, _saeubere_titeltext(" ".join(block))))
for name, eintrag in zip(("kompendium", "anwendungen"), absaetze):
gefunden[name] = eintrag
fehler = []
for name in ("titel", "untertitel", "kompendium", "anwendungen"):
if name not in gefunden:
print(f"FEHLER: {VORWORT_DATEI}: Der Titelblock enthaelt kein "
f"'{name}' - erwartet werden '#', '##' und zwei Absaetze.")
fehler.append(f"{VORWORT_DATEI}:{name}")
continue
nr, ist = gefunden[name]
if ist != quelle[name]:
print(f"FEHLER: {VORWORT_DATEI}:{nr}: '{name}' weicht von der "
f"Titelquelle ab.")
print(f" Vorwort: {ist}")
print(f" Quelle: {quelle[name]}")
fehler.append(f"{VORWORT_DATEI}:{nr}")
return fehler
# Dateien, die der Bau selbst in OR_HTML_04/assets/ erzeugt. Alles andere, was
# der Seitenkopf einbindet, muss aus der Quelle web_04/ kommen.
ERZEUGTE_ASSETS = {
@ -2172,22 +2242,25 @@ def baue_landingpage(html_verz: str, struktur: list[dict], labels: dict,
f'<section class="kapitelgruppe"><h2>{titel}</h2>'
f'<div class="kartenraster">{karten}</div></section>')
# Dieselbe Quelle wie die Titelseite des PDFs (NEUER_TITEL.md). Frueher
# standen die drei Absaetze hier fest im Quelltext - und wichen deshalb
# vom PDF ab, sobald der Titel ueberarbeitet wurde.
titeltexte = lies_titeltexte()
inhalt = f"""
<section class="hero">
<div class="hero-text">
<p class="hero-kicker">KOMPENDIUM</p>
<h1>{SITE_TITEL}</h1>
<p class="hero-untertitel">Von den mathematischen Grundlagen zur quantitativen
Portfolio-Optimierung an den Finanzmärkten.</p>
<p class="hero-untertitel">{titeltexte["untertitel"]}</p>
<div class="hero-badges">
<span class="badge badge-indigo">HiGHS</span>
<span class="badge badge-emerald">SciPy</span>
<span class="badge badge-cyan">Google OR-Tools</span>
<span class="badge badge-violett">CVXPY</span>
</div>
<p class="hero-kompendium"><em>Ein vollständiges Kompendium mit Algorithmen, Constraint
Programming, Vektor- und Matrixmethoden sowie modernen Python-Solvern (HiGHS, SciPy,
Google OR-Tools, CVXPY)</em></p>
<p class="hero-kompendium"><em>{titeltexte["kompendium"]}</em></p>
<p class="hero-anwendungen">{titeltexte["anwendungen"]}</p>
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
&lt;dieter(dot)schlueter(atsign)linix(dot)de&gt;<br>
Stand: {aktuelles_datum()}</p>

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# titeltexte_04.py
#
# Liest den Titeltext des Buchs aus seiner EINEN Quelle:
# Kritik_und_Verbesserungsvorschlaege/NEUER_TITEL.md
#
# Warum es dieses Modul gibt: Der Titeltext stand viermal im Repository - in
# NEUER_TITEL.md, in den Konstanten von bilder_04/erzeuge_titelseite.py (PDF),
# im Vorwort und fest einprogrammiert im Hero-Block von baue_landingpage()
# (Website). Drei Ueberarbeitungen des Titels zogen die ersten drei nach und
# liessen die vierte stehen: Die Startseite zeigte monatelang einen anderen
# Untertitel als das PDF, und der Anwendungen-Absatz fehlte dort ganz.
# Aufgefallen ist es niemandem, weil nichts die Fassungen verglichen hat.
#
# Jetzt lesen PDF-Titelseite und Startseite denselben Text ueber
# lies_titeltexte(); das Vorwort bleibt von Hand gepflegter Buchtext, wird aber
# von pruefe_dateien() gegen diese Quelle geprueft.
import os
import re
HIER = os.path.dirname(os.path.abspath(__file__))
BASIS = os.path.dirname(HIER)
TITEL_QUELLE = os.path.join(BASIS, "Kritik_und_Verbesserungsvorschlaege",
"NEUER_TITEL.md")
def _saeubern(text: str) -> str:
"""Entfernt Markdown-Hervorhebungen und zieht Zeilenumbrueche zu einfachen
Leerzeichen zusammen. Die Vorlage setzt die Fettungen anders als das
Vorwort - fuer den Vergleich zaehlt allein der Wortlaut."""
return " ".join(text.replace("**", "").replace("*", "").split())
def lies_titeltexte(pfad: str = TITEL_QUELLE) -> dict[str, str]:
"""Zerlegt NEUER_TITEL.md in seine vier Bestandteile.
Aufbau der Vorlage:
# <Titel>
### <Untertitel>
**<Kompendium-Absatz>**
<Anwendungen-Absatz>
Fehlt ein Teil, wird abgebrochen statt ein halb gefuelltes Titelblatt zu
erzeugen - ein Titelblatt ohne Untertitel faellt im PDF erst auf, wenn es
gedruckt ist."""
if not os.path.exists(pfad):
raise SystemExit(f"FEHLER: Die Quelle des Titeltexts fehlt: {pfad}")
with open(pfad, encoding="utf-8") as f:
inhalt = f.read()
bloecke = [b.strip() for b in re.split(r"\n\s*\n", inhalt) if b.strip()]
texte: dict[str, str] = {}
absaetze: list[str] = []
for block in bloecke:
if block.startswith("### "):
texte.setdefault("untertitel", _saeubern(block[4:]))
elif block.startswith("# "):
texte.setdefault("titel", _saeubern(block[2:]))
elif not block.startswith("#"):
absaetze.append(_saeubern(block))
if len(absaetze) >= 2:
texte["kompendium"], texte["anwendungen"] = absaetze[0], absaetze[1]
fehlend = [name for name in ("titel", "untertitel", "kompendium", "anwendungen")
if not texte.get(name)]
if fehlend:
raise SystemExit(
f"FEHLER: In {os.path.relpath(pfad, BASIS)} fehlt: "
f"{', '.join(fehlend)}.\n"
f" Erwartet werden eine '#'-Ueberschrift (Titel), eine "
f"'###'-Ueberschrift\n"
f" (Untertitel) und zwei Absaetze (Kompendium, Anwendungen).")
return texte
if __name__ == "__main__":
for name, text in lies_titeltexte().items():
print(f"{name}:\n {text}\n")

View file

@ -2192,6 +2192,47 @@ Seiten bei jedem Lauf neu, auch wenn sich nichts geändert hat; nach Zeitstempel
hätte das jedes Mal rund 19 MB sinnlose Übertragung bedeutet. Gemessen nach einem Neubau
ohne inhaltliche Änderung: **0 statt 151 Dateien**.
### 🐛 Zehnter Fund: Startseite und PDF-Titelseite zeigten verschiedene Texte
Die Frage lautete, warum die Titelseite der Online-Fassung einen anderen Text trägt als die
des PDFs. Die Antwort war messbar. Der Titeltext stand **viermal** im Repository:
`NEUER_TITEL.md`, die Konstanten in `bilder_04/erzeuge_titelseite.py`, der Titelblock des
Vorworts — und noch einmal fest einprogrammiert im Hero-Block von `baue_landingpage()`.
Die drei Commits, die den Titel überarbeitet haben (`ca5621f`, `199dd3b`, `1659fb9` im
Ursprungs-Repository), fassten jedes Mal dieselben drei Dateien an. **`build_version_04.py`
war in keinem davon**, und `OR_HTML_04/index.html` taucht in ihren Dateilisten überhaupt
nicht auf. Die Startseite zeigte deshalb:
| | PDF | Online (vorher) |
| --- | --- | --- |
| Unterzeile | „Von der mathematischen Modellierung zu praktischen intelligenten Lösungen für Betrieb, Technik und Finanzmärkte" | „Von den mathematischen Grundlagen zur quantitativen Portfolio-Optimierung an den Finanzmärkten." |
| Kompendium | „Ein **praxisorientiertes** Kompendium **für Entscheider** …" | „Ein **vollständiges** Kompendium mit **Algorithmen** …" |
| Anwendungen | vorhanden | **fehlte ganz** |
**Die Behandlung ist die inzwischen eingeübte:** Nicht die Kopie korrigieren, sondern die
Kopie abschaffen. `titeltexte_04.py` zerlegt `NEUER_TITEL.md` in Titel, Untertitel,
Kompendium- und Anwendungen-Absatz; PDF-Titelseite und Startseite lesen dieselbe Funktion.
Der Autor steht bewusst weiter als Konstante im Generator — die Vorlage beschreibt das Buch,
nicht sein Impressum.
**Der entscheidende Beleg:** Die neu erzeugte `titelseite.svg` ist **byte-identisch**
(`97fb40d7…`) zur vorherigen. Der Umbau hat am PDF nachweislich nichts verändert — auch
nicht am Titelumbruch: Die früher handgesetzten drei Zeilen („Optimierte" /
„Entscheidungsfindung" / „mit Python") entstehen jetzt automatisch aus `_umbrechen()`, und
zwar exakt so. Der Textvergleich über das SVG scheiterte übrigens: Es enthält **kein einziges
`<text>`-Element**, matplotlib zeichnet alle 726 Buchstaben als Kurven. Die Prüfsumme ist
hier der bessere Zeuge.
**Das Vorwort** bleibt von Hand gepflegter Buchtext, wird aber von `pruefe_titeltexte()` bei
jedem `--check` gegen die Quelle gestellt. Gleich beim ersten Lauf fand es einen echten
Unterschied: Das Vorwort setzte einen Geviertstrich („—"), die Vorlage einen
Halbgeviertstrich („–"). Im Deutschen ist der Halbgeviertstrich der richtige — das Vorwort
folgt jetzt der Quelle.
Damit ist jede der vier Stellen abgesichert: eine ist die Quelle, zwei werden daraus erzeugt,
eine wird geprüft.
---
## 8. Commit-Historie des V04-Strangs

View file

@ -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; neun Fälle wurden nachträglich gefunden, und jeder hat eine eigene
häufigsten verletzte; zehn Fälle wurden nachträglich gefunden, und jeder hat eine eigene
Prüfung hinterlassen.
### Externe Werkzeuge

View file

@ -28,8 +28,18 @@ Aufruf:
"""
import os
import sys
from datetime import datetime
# Der Titeltext kommt aus NEUER_TITEL.md, gelesen von titeltexte_04.py. Das
# Modul liegt im Quellverzeichnis des Buchs, dieses Skript in bilder_04/ -
# daher der Pfadeintrag. Ein zweiter Parser hier waere genau die Dopplung,
# die dieses Modul beseitigt.
_BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(_BASIS,
"Operations_Research_mit_Python_Version_04"))
from titeltexte_04 import lies_titeltexte
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
@ -192,26 +202,25 @@ def _setze_block(ax, zeilen, x, y0, groesse, durchschuss, **stil) -> float:
# Absaetze KOMPENDIUM und ANWENDUNGEN
# stehen gemeinsam in der grossen Solver-Karte.
#
# Im TITEL steht ein \n: An dieser Stelle wird bewusst umbrochen. Ohne ihn
# wuerde automatisch umbrochen und "mit Python" auseinandergerissen. Die Breite
# jeder Zeile wird in beiden Faellen geprueft.
# Sie stehen NICHT hier, sondern in NEUER_TITEL.md - derselben Datei, aus der
# auch die Startseite der Website ihren Hero-Block speist. Vorher trug dieses
# Skript eigene Konstanten, und die Startseite noch einmal eigene; drei
# Titelueberarbeitungen zogen diese hier nach und liessen jene stehen.
#
# Warum drei Zeilen und nicht zwei: "Entscheidungsfindung" ist bei 34 pt schon
# 429 pt breit und damit fast so breit wie der ganze Satzspiegel (453 pt). Jede
# zweizeilige Aufteilung braeuchte mindestens 649 pt - das passt bei keiner
# lesbaren Schriftgroesse.
TITEL = "Optimierte\nEntscheidungsfindung\nmit Python"
UNTERTITEL = ("Von der mathematischen Modellierung zu praktischen "
"intelligenten Lösungen für Betrieb, Technik und Finanzmärkte")
KOMPENDIUM = ("Ein praxisorientiertes Kompendium für Entscheider mit "
"Constraint Programming, Vektor- und Matrixmethoden sowie "
"HiGHS, SciPy, Google OR-Tools und CVXPY.")
ANWENDUNGEN = ("Von Personaleinsatz-, Schicht- und Vertretungsplänen, "
"Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung "
"über Energieverteilung "
"und Ressourcenplanung bis zur Portfoliooptimierung und besseren "
"Entscheidungen an den Finanzmärkten mit Python modellieren, "
"optimieren und belegbar fundierte Entscheidungen treffen.")
# Der TITEL kommt einzeilig an und wird von _umbrechen() auf den Satzspiegel
# verteilt. Das ergibt dieselben drei Zeilen wie die frueher handgesetzten
# ("Optimierte" / "Entscheidungsfindung" / "mit Python") - und das ist kein
# Zufall: "Entscheidungsfindung" ist bei 34 pt schon 429 pt breit und damit
# fast so breit wie der ganze Satzspiegel (453 pt). Jede zweizeilige Aufteilung
# braeuchte mindestens 649 pt und passt bei keiner lesbaren Schriftgroesse.
_TEXTE = lies_titeltexte()
TITEL = _TEXTE["titel"]
UNTERTITEL = _TEXTE["untertitel"]
KOMPENDIUM = _TEXTE["kompendium"]
ANWENDUNGEN = _TEXTE["anwendungen"]
# Der Autor steht nicht in der Titelvorlage - sie beschreibt das Buch, nicht
# sein Impressum.
AUTOR = "Autor / Herausgeber: Dieter Schlüter"
# Satzspiegel: linker Rand und rechte Kante wie Trennlinie und Kartenreihe.

View file

@ -309,6 +309,7 @@ article li { margin: .3em 0; }
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
.hero-badges { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
.hero-kompendium { color: var(--muted); margin-top: 1.4rem; max-width: 640px; }
.hero-anwendungen { color: var(--muted); margin-top: .8rem; max-width: 640px; }
.hero-autor { color: var(--muted); font-size: .92rem; line-height: 1.5; }
.badge { padding: .35em .9em; border-radius: 20px; color: #fff; font-weight: 600; font-size: .85rem; }
.badge-indigo { background: var(--accent); }