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:
parent
47d63dfe9c
commit
022bd4eaca
15 changed files with 250 additions and 37 deletions
14
CLAUDE.md
14
CLAUDE.md
|
|
@ -34,7 +34,8 @@ OR_HTML_04/ generiert: Mehrseiten-Website
|
||||||
Operations_Research_mit_Python_Version_04_Programme/ generiert: Beispielprogramme
|
Operations_Research_mit_Python_Version_04_Programme/ generiert: Beispielprogramme
|
||||||
Notebooks_04/ generiert: ein .ipynb je Kapitel
|
Notebooks_04/ generiert: ein .ipynb je Kapitel
|
||||||
Kritik_und_Verbesserungsvorschlaege/ Quelle: Rezension, Verbesserungsvorschläge,
|
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
|
pyproject.toml Quelle: Abhängigkeiten in Gruppen
|
||||||
Dockerfile, .dockerignore Quelle: Kurs-Image (Programme + JupyterLab)
|
Dockerfile, .dockerignore Quelle: Kurs-Image (Programme + JupyterLab)
|
||||||
LICENSE, LICENSE-TEXT.md MIT fuer Code, CC BY-SA 4.0 fuer den Text
|
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
|
## Die wichtigsten Konventionen
|
||||||
|
|
||||||
**Keine abgeleiteten Zahlen im Quelltext.** Das ist die Regel, an der dieses Projekt am
|
**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,
|
(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
|
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`
|
Seit dem letzten Fund trägt **keine** Marke mehr eine handgeschriebene Nummer, und `--check`
|
||||||
bewacht jede Familie. Konkret:
|
bewacht jede Familie. Konkret:
|
||||||
|
|
||||||
|
|
@ -107,6 +109,12 @@ bewacht jede Familie. Konkret:
|
||||||
einer bereits nummerierten Überschrift. Verwiesen wird auf
|
einer bereits nummerierten Überschrift. Verwiesen wird auf
|
||||||
`{ref:sec:<kapitel>-denkfehler}`.
|
`{ref:sec:<kapitel>-denkfehler}`.
|
||||||
* Im Fließtext `{ref:<label>}`, nie eine Literalzahl.
|
* 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
|
* **Die Dateiübersicht in `Operations_Research_mit_Python_Version_04/README.md`** steht
|
||||||
zwischen `<!-- KAPITELTABELLE:ANFANG -->` und `<!-- KAPITELTABELLE:ENDE -->` und wird von
|
zwischen `<!-- KAPITELTABELLE:ANFANG -->` und `<!-- KAPITELTABELLE:ENDE -->` und wird von
|
||||||
`schreibe_kapiteltabelle()` aus derselben Struktur erzeugt wie die Nummern im Buch. Nicht
|
`schreibe_kapiteltabelle()` aus derselben Struktur erzeugt wie die Nummern im Buch. Nicht
|
||||||
|
|
|
||||||
BIN
OR_HTML_04/Operations_Research_mit_Python_Version_04.pdf
generated
BIN
OR_HTML_04/Operations_Research_mit_Python_Version_04.pdf
generated
Binary file not shown.
1
OR_HTML_04/assets/site.css
generated
1
OR_HTML_04/assets/site.css
generated
|
|
@ -309,6 +309,7 @@ article li { margin: .3em 0; }
|
||||||
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
|
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
|
||||||
.hero-badges { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
|
.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-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; }
|
.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 { padding: .35em .9em; border-radius: 20px; color: #fff; font-weight: 600; font-size: .85rem; }
|
||||||
.badge-indigo { background: var(--accent); }
|
.badge-indigo { background: var(--accent); }
|
||||||
|
|
|
||||||
2
OR_HTML_04/gesamtdokument.html
generated
2
OR_HTML_04/gesamtdokument.html
generated
|
|
@ -378,7 +378,7 @@
|
||||||
<h1 class="unnumbered" id="optimierte-entscheidungsfindung-mit-python">Optimierte Entscheidungsfindung mit Python</h1>
|
<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>
|
<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><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>Autor / Herausgeber: Dieter Schlüter</p>
|
||||||
<p><dieter(dot)schlueter(atsign)linix(dot)de></p>
|
<p><dieter(dot)schlueter(atsign)linix(dot)de></p>
|
||||||
<p>Stand: 8. September 2026</p>
|
<p>Stand: 8. September 2026</p>
|
||||||
|
|
|
||||||
8
OR_HTML_04/index.html
generated
8
OR_HTML_04/index.html
generated
|
|
@ -104,17 +104,15 @@
|
||||||
<div class="hero-text">
|
<div class="hero-text">
|
||||||
<p class="hero-kicker">KOMPENDIUM</p>
|
<p class="hero-kicker">KOMPENDIUM</p>
|
||||||
<h1>Optimierte Entscheidungsfindung mit Python</h1>
|
<h1>Optimierte Entscheidungsfindung mit Python</h1>
|
||||||
<p class="hero-untertitel">Von den mathematischen Grundlagen zur quantitativen
|
<p class="hero-untertitel">Von der mathematischen Modellierung zu praktischen intelligenten Lösungen für Betrieb, Technik und Finanzmärkte</p>
|
||||||
Portfolio-Optimierung an den Finanzmärkten.</p>
|
|
||||||
<div class="hero-badges">
|
<div class="hero-badges">
|
||||||
<span class="badge badge-indigo">HiGHS</span>
|
<span class="badge badge-indigo">HiGHS</span>
|
||||||
<span class="badge badge-emerald">SciPy</span>
|
<span class="badge badge-emerald">SciPy</span>
|
||||||
<span class="badge badge-cyan">Google OR-Tools</span>
|
<span class="badge badge-cyan">Google OR-Tools</span>
|
||||||
<span class="badge badge-violett">CVXPY</span>
|
<span class="badge badge-violett">CVXPY</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="hero-kompendium"><em>Ein vollständiges Kompendium mit Algorithmen, Constraint
|
<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>
|
||||||
Programming, Vektor- und Matrixmethoden sowie modernen Python-Solvern (HiGHS, SciPy,
|
<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>
|
||||||
Google OR-Tools, CVXPY)</em></p>
|
|
||||||
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
|
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
|
||||||
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
||||||
Stand: 8. September 2026</p>
|
Stand: 8. September 2026</p>
|
||||||
|
|
|
||||||
2
OR_HTML_04/vorwort.html
generated
2
OR_HTML_04/vorwort.html
generated
|
|
@ -107,7 +107,7 @@
|
||||||
<h1 class="unnumbered" id="optimierte-entscheidungsfindung-mit-python">Optimierte Entscheidungsfindung mit Python</h1>
|
<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>
|
<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><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>Autor / Herausgeber: Dieter Schlüter</p>
|
||||||
<p><dieter(dot)schlueter(atsign)linix(dot)de></p>
|
<p><dieter(dot)schlueter(atsign)linix(dot)de></p>
|
||||||
<p>Stand: 8. September 2026</p>
|
<p>Stand: 8. September 2026</p>
|
||||||
|
|
|
||||||
2
Operations_Research_mit_Python_Version_04.md
generated
2
Operations_Research_mit_Python_Version_04.md
generated
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
**Ein praxisorientiertes Kompendium für Entscheider** mit Constraint Programming, Vektor- und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.
|
**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
|
Autor / Herausgeber: Dieter Schlüter
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -23,7 +23,7 @@ und Matrixmethoden sowie HiGHS, SciPy, Google OR-Tools und CVXPY.
|
||||||
|
|
||||||
Von **Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und
|
Von **Personaleinsatz-, Schicht- und Vertretungsplänen, Fahrtrouten, Logistik und
|
||||||
Maschinen- und Netzwerkauslastung** über **Energieverteilung und Ressourcenplanung** bis zur
|
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.
|
modellieren, optimieren und belegbar fundierte Entscheidungen treffen.
|
||||||
|
|
||||||
Autor / Herausgeber: Dieter Schlüter
|
Autor / Herausgeber: Dieter Schlüter
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
from titeltexte_04 import lies_titeltexte, _saeubern as _saeubere_titeltext
|
||||||
|
|
||||||
HIER = os.path.dirname(os.path.abspath(__file__))
|
HIER = os.path.dirname(os.path.abspath(__file__))
|
||||||
BASIS = os.path.dirname(HIER)
|
BASIS = os.path.dirname(HIER)
|
||||||
|
|
||||||
|
|
@ -389,9 +391,77 @@ def pruefe_dateien() -> list[str]:
|
||||||
fehlend.append(name)
|
fehlend.append(name)
|
||||||
|
|
||||||
fehlend.extend(pruefe_assets())
|
fehlend.extend(pruefe_assets())
|
||||||
|
fehlend.extend(pruefe_titeltexte())
|
||||||
return fehlend
|
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
|
# Dateien, die der Bau selbst in OR_HTML_04/assets/ erzeugt. Alles andere, was
|
||||||
# der Seitenkopf einbindet, muss aus der Quelle web_04/ kommen.
|
# der Seitenkopf einbindet, muss aus der Quelle web_04/ kommen.
|
||||||
ERZEUGTE_ASSETS = {
|
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'<section class="kapitelgruppe"><h2>{titel}</h2>'
|
||||||
f'<div class="kartenraster">{karten}</div></section>')
|
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"""
|
inhalt = f"""
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero-text">
|
<div class="hero-text">
|
||||||
<p class="hero-kicker">KOMPENDIUM</p>
|
<p class="hero-kicker">KOMPENDIUM</p>
|
||||||
<h1>{SITE_TITEL}</h1>
|
<h1>{SITE_TITEL}</h1>
|
||||||
<p class="hero-untertitel">Von den mathematischen Grundlagen zur quantitativen
|
<p class="hero-untertitel">{titeltexte["untertitel"]}</p>
|
||||||
Portfolio-Optimierung an den Finanzmärkten.</p>
|
|
||||||
<div class="hero-badges">
|
<div class="hero-badges">
|
||||||
<span class="badge badge-indigo">HiGHS</span>
|
<span class="badge badge-indigo">HiGHS</span>
|
||||||
<span class="badge badge-emerald">SciPy</span>
|
<span class="badge badge-emerald">SciPy</span>
|
||||||
<span class="badge badge-cyan">Google OR-Tools</span>
|
<span class="badge badge-cyan">Google OR-Tools</span>
|
||||||
<span class="badge badge-violett">CVXPY</span>
|
<span class="badge badge-violett">CVXPY</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="hero-kompendium"><em>Ein vollständiges Kompendium mit Algorithmen, Constraint
|
<p class="hero-kompendium"><em>{titeltexte["kompendium"]}</em></p>
|
||||||
Programming, Vektor- und Matrixmethoden sowie modernen Python-Solvern (HiGHS, SciPy,
|
<p class="hero-anwendungen">{titeltexte["anwendungen"]}</p>
|
||||||
Google OR-Tools, CVXPY)</em></p>
|
|
||||||
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
|
<p class="hero-autor">Autor / Herausgeber: Dieter Schlüter<br>
|
||||||
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
||||||
Stand: {aktuelles_datum()}</p>
|
Stand: {aktuelles_datum()}</p>
|
||||||
|
|
|
||||||
82
Operations_Research_mit_Python_Version_04/titeltexte_04.py
Normal file
82
Operations_Research_mit_Python_Version_04/titeltexte_04.py
Normal 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")
|
||||||
41
PROGRESS.md
41
PROGRESS.md
|
|
@ -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
|
hätte das jedes Mal rund 19 MB sinnlose Übertragung bedeutet. Gemessen nach einem Neubau
|
||||||
ohne inhaltliche Änderung: **0 statt 151 Dateien**.
|
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
|
## 8. Commit-Historie des V04-Strangs
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ python3 bilder_04/erzeuge_kondition.py
|
||||||
`--check` prüft mehr als die Struktur: fehlende Codezäune, tote Dateilinks, eine gebrochene
|
`--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
|
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
|
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.
|
Prüfung hinterlassen.
|
||||||
|
|
||||||
### Externe Werkzeuge
|
### Externe Werkzeuge
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,18 @@ Aufruf:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from datetime import datetime
|
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
|
import matplotlib
|
||||||
matplotlib.use("Agg")
|
matplotlib.use("Agg")
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
@ -192,26 +202,25 @@ def _setze_block(ax, zeilen, x, y0, groesse, durchschuss, **stil) -> float:
|
||||||
# Absaetze KOMPENDIUM und ANWENDUNGEN
|
# Absaetze KOMPENDIUM und ANWENDUNGEN
|
||||||
# stehen gemeinsam in der grossen Solver-Karte.
|
# stehen gemeinsam in der grossen Solver-Karte.
|
||||||
#
|
#
|
||||||
# Im TITEL steht ein \n: An dieser Stelle wird bewusst umbrochen. Ohne ihn
|
# Sie stehen NICHT hier, sondern in NEUER_TITEL.md - derselben Datei, aus der
|
||||||
# wuerde automatisch umbrochen und "mit Python" auseinandergerissen. Die Breite
|
# auch die Startseite der Website ihren Hero-Block speist. Vorher trug dieses
|
||||||
# jeder Zeile wird in beiden Faellen geprueft.
|
# 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
|
# Der TITEL kommt einzeilig an und wird von _umbrechen() auf den Satzspiegel
|
||||||
# 429 pt breit und damit fast so breit wie der ganze Satzspiegel (453 pt). Jede
|
# verteilt. Das ergibt dieselben drei Zeilen wie die frueher handgesetzten
|
||||||
# zweizeilige Aufteilung braeuchte mindestens 649 pt - das passt bei keiner
|
# ("Optimierte" / "Entscheidungsfindung" / "mit Python") - und das ist kein
|
||||||
# lesbaren Schriftgroesse.
|
# Zufall: "Entscheidungsfindung" ist bei 34 pt schon 429 pt breit und damit
|
||||||
TITEL = "Optimierte\nEntscheidungsfindung\nmit Python"
|
# fast so breit wie der ganze Satzspiegel (453 pt). Jede zweizeilige Aufteilung
|
||||||
UNTERTITEL = ("Von der mathematischen Modellierung zu praktischen "
|
# braeuchte mindestens 649 pt und passt bei keiner lesbaren Schriftgroesse.
|
||||||
"intelligenten Lösungen für Betrieb, Technik und Finanzmärkte")
|
_TEXTE = lies_titeltexte()
|
||||||
KOMPENDIUM = ("Ein praxisorientiertes Kompendium für Entscheider mit "
|
TITEL = _TEXTE["titel"]
|
||||||
"Constraint Programming, Vektor- und Matrixmethoden sowie "
|
UNTERTITEL = _TEXTE["untertitel"]
|
||||||
"HiGHS, SciPy, Google OR-Tools und CVXPY.")
|
KOMPENDIUM = _TEXTE["kompendium"]
|
||||||
ANWENDUNGEN = ("Von Personaleinsatz-, Schicht- und Vertretungsplänen, "
|
ANWENDUNGEN = _TEXTE["anwendungen"]
|
||||||
"Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung "
|
|
||||||
"über Energieverteilung "
|
# Der Autor steht nicht in der Titelvorlage - sie beschreibt das Buch, nicht
|
||||||
"und Ressourcenplanung bis zur Portfoliooptimierung und besseren "
|
# sein Impressum.
|
||||||
"Entscheidungen an den Finanzmärkten – mit Python modellieren, "
|
|
||||||
"optimieren und belegbar fundierte Entscheidungen treffen.")
|
|
||||||
AUTOR = "Autor / Herausgeber: Dieter Schlüter"
|
AUTOR = "Autor / Herausgeber: Dieter Schlüter"
|
||||||
|
|
||||||
# Satzspiegel: linker Rand und rechte Kante wie Trennlinie und Kartenreihe.
|
# Satzspiegel: linker Rand und rechte Kante wie Trennlinie und Kartenreihe.
|
||||||
|
|
|
||||||
|
|
@ -309,6 +309,7 @@ article li { margin: .3em 0; }
|
||||||
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
|
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
|
||||||
.hero-badges { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
|
.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-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; }
|
.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 { padding: .35em .9em; border-radius: 20px; color: #fff; font-weight: 600; font-size: .85rem; }
|
||||||
.badge-indigo { background: var(--accent); }
|
.badge-indigo { background: var(--accent); }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue