operations_research/OR_HTML_04/programme/Excel_Bruecke.py
dschlueter b7af2f1d9a Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.

Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.

Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.

Zusaetzlich in diesem Commit:

* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
  figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
  unveraendert daneben bestehen. ortools steht in der Grundausstattung,
  highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
  schlanken Installation gar nicht erst auftreten.

* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
  importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
  steht in requirements.txt, wird aber von keinem Programm importiert,
  sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
  [empfehlungen]).

* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
  ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
  Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.

* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
  tatsaechlichen aus erzeuge_titelseite.py korrigiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00

96 lines
3.6 KiB
Python

#!/usr/bin/env python3
# Excel_Bruecke.py
"""
Kapitel Einfuehrung: Vom Excel-Solver zu Python - dieselbe Rechnung, dieselbe Datei.
Das Programm bildet den vollstaendigen Arbeitsweg ab, den ein Excel-Modell im
Betrieb sonst von Hand geht:
Tabellenblatt lesen -> Modell bauen -> loesen -> Ergebnis zurueckschreiben
Damit es ohne Vorbereitung laeuft, legt es die Eingabedatei beim ersten Start
selbst an. Ersetzen Sie sie durch Ihre eigene Datei - solange die Spalten
gleich heissen, aendert sich am Code nichts.
Die vier Arbeitsschritte kommen aus or_kern.py (Kapitel Praxisfallen). Was
hier steht, ist nur noch das, was diesen Fall ausmacht - der Rest ist
gemeinsamer Unterbau. Wer das Modul nicht kennt: Es ist im Buch vollstaendig
abgedruckt und liegt im selben Verzeichnis.
Erzeugte Dateien (im Arbeitsverzeichnis):
produktionsmix.xlsx Eingabe: Blaetter 'Produkte' und 'Kapazitaeten'
produktionsmix_ergebnis.xlsx Ausgabe: Blaetter 'Plan' und 'Kennzahlen'
Benoetigt: pandas, openpyxl, ortools, pydantic (ueber or_kern)
"""
from __future__ import annotations
import os
import pandas as pd
from or_kern import (lade_produktionsproblem, loese_mit_glop, pruefe_loesung,
schreibe_ergebnis)
EINGABE = "produktionsmix.xlsx"
AUSGABE = "produktionsmix_ergebnis.xlsx"
def lege_beispieldatei_an(pfad: str) -> None:
"""Schreibt die Eingabemappe, wie sie auch aus dem Controlling kaeme."""
produkte = pd.DataFrame({
"Produkt": ["Tisch", "Stuhl", "Regal"],
"Deckungsbeitrag": [240.0, 60.0, 130.0],
"Montagestunden": [3.0, 1.0, 2.0],
"Plattenmaterial": [6.0, 1.0, 4.0],
})
kapazitaeten = pd.DataFrame({
"Ressource": ["Montagestunden", "Plattenmaterial"],
"Verfuegbar": [150.0, 240.0],
})
with pd.ExcelWriter(pfad, engine="openpyxl") as mappe:
produkte.to_excel(mappe, sheet_name="Produkte", index=False)
kapazitaeten.to_excel(mappe, sheet_name="Kapazitaeten", index=False)
if __name__ == "__main__":
if not os.path.exists(EINGABE):
lege_beispieldatei_an(EINGABE)
print(f"Beispiel-Eingabedatei angelegt: {EINGABE}")
# 1. Lesen und pruefen. Faellt hier etwas auf, ist der Fehler noch
# zuzuordnen - spaeter waere er nur noch eine merkwuerdige Zahl.
problem = lade_produktionsproblem(EINGABE)
# 2. Loesen. Einzige solverabhaengige Zeile des ganzen Programms -
# 'loese_mit_scipy' waere ein Einzeiler-Wechsel.
loesung = loese_mit_glop(problem)
# 3. Abnahmepruefung gegen die Anforderungen, ohne den Solver zu fragen.
beanstandungen = pruefe_loesung(problem, loesung)
if beanstandungen:
raise SystemExit("Abnahmepruefung fehlgeschlagen:\n - "
+ "\n - ".join(beanstandungen))
# 4. Zurueckschreiben - im Format, das die Fachabteilung ohnehin benutzt.
schreibe_ergebnis(AUSGABE, problem, loesung)
print("=" * 66)
print(" PRODUKTIONSPLAN AUS DER EXCEL-MAPPE")
print("=" * 66)
for produkt in problem.produkte:
menge = loesung.werte[produkt.name]
print(f"{produkt.name:<18} {menge:8.1f} Stueck "
f"{menge * produkt.deckungsbeitrag:12,.2f} EUR")
print("-" * 66)
print(loesung.als_bericht())
print("Abnahmepruefung: bestanden\n")
# Die Schattenpreise beantworten die Frage, die der Plan nicht stellt:
# Was waere eine zusaetzliche Einheit dieser Ressource wert?
for ressource, preis in loesung.schattenpreise.items():
print(f" eine Einheit {ressource:<18} mehr waere wert: {preis:7.2f} EUR")
print("=" * 66)
print(f"Ergebnis geschrieben nach: {AUSGABE}")