Drei Aufraeumarbeiten - und zwei Funde, die dabei auffielen.
1. DIE PNG-ZWEITFASSUNGEN SIND WEG. Jedes Diagramm lag doppelt vor, als SVG
und als PNG, und kein einziges src=/href= in der Website zeigte je auf ein
PNG. Der Build kopierte sie trotzdem mit: 3,3 MB im Repository plus 3,3 MB,
die bei jeder Veroeffentlichung auf den Webserver gingen. Die 15 Generatoren
schreiben jetzt nur noch SVG, die Docstrings sind mitgezogen. Vor dem
Loeschen geprueft: Jedes PNG hatte sein gleichnamiges SVG, alle 33 waren
versioniert.
FUND 1: erzeuge_kap06_gantt.py folgte als einziger Generator nicht der
Konvention - weder svg.hashsalt noch metadata={"Date": None}. Sein SVG trug
einen echten Zeitstempel und bei jedem Lauf andere clip-path-IDs, war also
nie byteidentisch reproduzierbar, obwohl CLAUDE.md genau das fuer alle
Generatoren festhaelt. Aufgefallen nur, weil nach der PNG-Umstellung 32 von
33 SVGs bitgleich blieben und eines nicht. Jetzt byteidentisch ueber zwei
Laeufe.
FUND 2: spiegle_bilder() legte leere Verzeichnisse auf dem Webserver an. Der
Dateifilter arbeitete korrekt, aber os.walk durchlief auch __pycache__/, und
os.makedirs() erzeugte es am Ziel. Die Verzeichnisliste wird jetzt vorher
gefiltert. Gegengetestet.
2. BAU-UEBERBLEIBSEL entfernt (alle ignoriert und neu erzeugbar):
svg-inkscape/, build_v04.log, .pytest_cache/, Programme/output/, vier
__pycache__/ und die Excel-Mappen. Arbeitsbaum 36 -> 33 MB, danach null
ignorierte Ueberbleibsel.
3. Excel_Bruecke.py SCHREIBT NEBEN DAS SKRIPT statt ins
Arbeitsverzeichnis. Es benutzte blanke relative Namen; wer es aus der
Repository-Wurzel startete, verstreute dort produktionsmix.xlsx und
produktionsmix_ergebnis.xlsx. Jetzt wie die vier anderen schreibenden
Programme ueber os.path.dirname(os.path.abspath(__file__)). Nachgemessen:
Lauf aus der Wurzel legt dort null Dateien ab.
Ausserdem git gc: 653 lose Objekte gepackt, .git von 71 MB auf 28 MB -
reines Repacken, kein Inhalt beruehrt.
Geprueft: alle 33 im Buch referenzierten SVGs vorhanden, in Quelle und
Website; 16 Generatoren fehlerfrei; keine fehlenden Bilder im LaTeX-Lauf;
33 pytest-Tests; PDF unveraendert 760 Seiten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
3.9 KiB
Python
101 lines
3.9 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)
|
|
|
|
# Die Mappen liegen NEBEN diesem Programm, nicht im aktuellen
|
|
# Arbeitsverzeichnis. Sonst haengt es davon ab, aus welchem Ordner man das
|
|
# Skript startet, wo die Dateien landen - und wer es aus der
|
|
# Repository-Wurzel aufruft, verstreut sie dort.
|
|
HIER = os.path.dirname(os.path.abspath(__file__))
|
|
EINGABE = os.path.join(HIER, "produktionsmix.xlsx")
|
|
AUSGABE = os.path.join(HIER, "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}")
|