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
|
||||
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
|
||||
|
|
|
|||
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-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); }
|
||||
|
|
|
|||
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>
|
||||
<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><dieter(dot)schlueter(atsign)linix(dot)de></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">
|
||||
<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>
|
||||
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
||||
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>
|
||||
<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><dieter(dot)schlueter(atsign)linix(dot)de></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.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
<dieter(dot)schlueter(atsign)linix(dot)de><br>
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue