unnötige variablen rausgelöscht

This commit is contained in:
Nicolai 2026-04-24 11:32:38 +02:00
parent 16633f25b1
commit 4bf7d47007
5 changed files with 571 additions and 23 deletions

217
AGENTS.md Normal file
View File

@ -0,0 +1,217 @@
in Agents.md steht alles drin, was du brauchst# AGENTS.md
## Projektkontext
LEAG-COALLOG ist ein Proof of Concept zur Optimierung der Braunkohle-Logistik.
Das System verarbeitet eine Excel-Parameterdatei, erzeugt daraus normalisierte
Parquet-Tabellen und loest anschliessend ein Pyomo-Optimierungsmodell fuer
Lieferungen von Tagebauen zu Kraftwerken und Veredlung.
Ziel ist eine planbare Rohkohleverteilung unter Beruecksichtigung von Nachfrage,
Toleranzen, Mischungsverhaeltnissen, Foerder- und Verladungskapazitaeten,
Verfuegbarkeiten, Schichtmustern, Bunkerlogik und Solver-Limits. Die Webapp dient
als Bedienoberflaeche fuer Upload, Parametrisierung, Laufstatus, Logs,
Visualisierung und Ergebnisdownload.
## Architektur
Die Anwendung besteht aus drei Hauptteilen:
1. **Preprocessing**
- Datei: `src/preprocessing/exploration_preprocess.py`
- Liest die Excel-Datei aus `POC1_INPUT_XLSX`.
- Schreibt vorbereitete Tabellen als Parquet nach `POC1_OUTPUT_DIR`.
- Die Logik stammt aus einem Notebook-Export und ist stark an die Struktur der
Eingabe-Excel gebunden.
2. **Optimierung**
- Einstieg: `src/optimization/run_optimization.py`
- Modell: `src/optimization/model_builder.py`
- Laedt alle Parquet-Dateien aus einem Datenverzeichnis.
- Baut ein Pyomo `ConcreteModel`.
- Loest mit `highs`, `gurobi` oder `scip`.
- Exportiert `output.xlsx` und optional `warmstart.json`.
3. **Webapp**
- Backend: `webapp/backend/main.py`
- Frontend: `webapp/frontend/src/App.jsx`
- FastAPI nimmt Uploads entgegen, legt Job-Verzeichnisse unter `var/jobs` an
und startet Preprocessing und Optimierung als Subprozesse.
- React/Vite stellt Upload, Solver-Parameter, Job-Status, Logs, Plots,
Warmstart und Downloads bereit.
## Datenfluss
Standardablauf eines Webapp-Laufs:
1. Browser sendet Excel-Datei, Solver und Parameter an `POST /api/run`.
2. FastAPI erstellt ein Job-Verzeichnis unter `var/jobs/<job_id>/`.
3. Die Upload-Datei wird als `input.xlsx` gespeichert.
4. `exploration_preprocess.py` schreibt `processed/*.parquet`.
5. `run_optimization.py` liest `processed/*.parquet`, baut und loest das Modell.
6. Ergebnisse landen in `output/output.xlsx` und `output/warmstart.json`.
7. Logs liegen unter `logs/preprocess.log` und `logs/optimization.log`.
8. Das Frontend pollt Status, Logs und abgeleitete Visualisierungsdaten.
Wichtige API-Endpunkte:
- `GET /api/health`
- `POST /api/run`
- `GET /api/jobs/{job_id}`
- `POST /api/jobs/{job_id}/cancel`
- `GET /api/jobs/{job_id}/output`
- `GET /api/jobs/{job_id}/warmstart`
- `GET /api/jobs/{job_id}/monthly-flows`
- `GET /api/jobs/{job_id}/capacity-timeseries`
- `GET /api/jobs/{job_id}/logs/{log_name}`
- `GET /api/jobs/{job_id}/logs/{log_name}/stream`
## Optimierungsmodell
Das Modell bildet Liefermengen als ganzzahlige Schritte ab:
- Quellen `I`: `Reichwalde`, `Nochten`, `Welzow`
- Ziele `J`: `J`, `SP`, `B3`, `B4`, `V`
- Tage `D`: Bergbauwoche Samstag bis Freitag
- Schichten `S`: `F`, `S`, `N`
- Entscheidung `k[i,j,w,d,s]`: ganzzahlige Anzahl Lieferschritte
- Fluss `x[i,j,w,d,s] = step_size_tonnes * k[i,j,w,d,s]`
Wichtige Nebenbedingungen und Konzepte:
- Tages-, Wochen- und Monatstoleranzen fuer Kraftwerke
- separate Veredlungsbedarfe fuer Nochten- und Welzower-Kohle
- harte Mix-Min/Max-Grenzen und weiche Zielabweichungen
- Foerderkapazitaeten pro Monat
- Verladungskapazitaeten pro Schicht und Tag
- Verfuegbarkeiten aus `Verfuegbarkeiten.parquet`
- feste Routenverbote und kombinierte Flussgrenzen
- Schichtglaettung und Schichtmuster
- optionale Bunkerlogik fuer Ziele mit Bunkerparametern
- Warmstart Import/Export fuer Folgelaeufe
Weitere fachliche Details stehen in:
- `README.md`
- `docs/app-kommunikation.md`
- `notebooks/constraints_overview.md`
- `latex/modellierung.tex`
## Wichtige Verzeichnisse
- `src/preprocessing/`: Excel-zu-Parquet-Datenaufbereitung
- `src/optimization/`: Pyomo-Modell, Solver-Lauf, Ergebnisexport
- `webapp/backend/`: FastAPI-App und Job-Orchestrierung
- `webapp/frontend/`: React/Vite-Frontend
- `data/input/`: lokale Eingabedateien
- `data/processed/`: lokale Preprocessing-Ausgaben
- `data/out/`: lokale Ergebnisartefakte
- `var/jobs/`: persistente Webapp-Jobdaten
- `docs/`: Architektur- und Kommunikationsdokumentation
- `notebooks/`: Analyse- und Modellnotizen
- `results/`: Solver-Debugartefakte wie `iis.ilp`
## Lokale Kommandos
Backend starten:
```bash
uv run python -m uvicorn webapp.backend.main:app --reload
```
Frontend starten:
```bash
cd webapp/frontend
npm install
npm run dev
```
Preprocessing direkt ausfuehren:
```bash
uv run python src/preprocessing/exploration_preprocess.py
```
Optimierung direkt ausfuehren:
```bash
uv run python src/optimization/run_optimization.py --data-dir data/processed --solver highs
```
Docker-All-in-One:
```bash
docker compose up --build
```
## Laufzeit und Solver
Unterstuetzte Solver sind aktuell `highs`, `gurobi` und `scip`, sofern lokal
installiert und fuer Pyomo verfuegbar. Im Docker-Image wird `gurobipy`
installiert. Fuer Gurobi wird entweder eine Lizenzdatei ueber
`GRB_LICENSE_FILE` oder WLS-Umgebungsvariablen verwendet:
- `GRB_LICENSE_FILE`
- `GRB_WLSACCESSID`
- `GRB_WLSSECRET`
- `GRB_LICENSEID`
Die Webapp prueft Solver-Verfuegbarkeit ueber `/api/health`.
## Entwicklungsregeln fuer Agenten
- Aendere keine Eingabe-, Ergebnis- oder Jobdaten ohne expliziten Grund.
- Behandle `var/jobs`, `data/out`, `data/processed` und `results` als erzeugte
Artefakte, sofern die Aufgabe nicht genau diese Dateien betrifft.
- Respektiere bestehende fachliche Logik im Optimierungsmodell. Viele Grenzen
sind hart kodiert und fachlich motiviert.
- Bei Aenderungen an `model_builder.py` immer pruefen, ob Export,
Visualisierung und Warmstart noch konsistent sind.
- Bei Aenderungen am Excel-Output die Backend-Parser in `webapp/backend/main.py`
mitdenken. Sie lesen konkrete Sheet- und Spaltenstrukturen.
- Bei Aenderungen an API-Antworten das Frontend in `webapp/frontend/src/App.jsx`
synchron halten.
- Die Preprocessing-Datei ist notebookartig und teilweise redundant. Kleine,
lokale Korrekturen sind besser als grosse Refactors ohne Tests.
- Keine Gurobi-Lizenzdateien, Credentials oder lokale Pfade committen.
- UI und Backend muessen auch ohne Gurobi sinnvoll starten koennen, wenn HiGHS
verfuegbar ist.
## Validierung
Es gibt derzeit keine klar etablierte automatisierte Testsuite. Je nach Aenderung
sind diese Checks sinnvoll:
```bash
uv run python src/preprocessing/exploration_preprocess.py
uv run python src/optimization/run_optimization.py --data-dir data/processed --solver highs --time-limit 60
cd webapp/frontend && npm run build
```
Bei Backend- oder Webapp-Aenderungen zusaetzlich:
```bash
uv run python -m uvicorn webapp.backend.main:app --reload
```
Dann im Browser oder per API pruefen:
- `/api/health`
- Upload ueber Frontend
- Statuspolling
- Logs
- Download von `output.xlsx`
- Download von `warmstart.json`
- Plots fuer Monatsfluesse und Kapazitaetszeitreihen
## Bekannte Besonderheiten
- Die Bergbauwoche beginnt fachlich am Samstag. Im Modell wird dafuer ein
Datum-plus-zwei-Tage-Shift zur ISO-Woche verwendet.
- Lieferabweichungen und Mischungsverhaeltnisse beziehen sich auf Lieferungen,
nicht auf Bunkerabfluss.
- `no_three_in_a_row` ist laut README aktuell auf `<= 3` gelockert.
- Bei Infeasibility kann Gurobi ein IIS nach `results/iis.ilp` schreiben.
- Der Projektname in Metadaten und README ist teilweise noch `POC1`; fachlich
entspricht das dem aktuellen LEAG-COALLOG-Prototyp.

252
COALLOG.drawio Normal file
View File

@ -0,0 +1,252 @@
<mxfile host="app.diagrams.net">
<diagram name="Seite-1" id="P-KqJ_aeJxb4NgLkL2IL">
<mxGraphModel dx="1132" dy="605" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="LSBXT6lm1qTOuGE88BYh-1" parent="1" style="whiteSpace=wrap;strokeWidth=2;" value="Benutzer" vertex="1">
<mxGeometry height="54" width="124" x="20" y="275" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-2" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=#f8cecc;strokeColor=#b85450;" value="Excel Input&#xa;Planungsdaten" vertex="1">
<mxGeometry height="78" width="164" x="322" y="222" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-3" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=#ffe6cc;strokeColor=#d79b00;" value="React Frontend&#xa;Vite Build / App.jsx" vertex="1">
<mxGeometry height="78" width="200" x="672" y="263" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-4" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=#ffe6cc;strokeColor=#d79b00;" value="FastAPI Backend&#xa;webapp/backend/main.py" vertex="1">
<mxGeometry height="78" width="250" x="1128" y="257" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-5" parent="1" style="shape=cylinder3;boundedLbl=1;backgroundOutline=1;size=10;strokeWidth=2;whiteSpace=wrap;fillColor=#f8cecc;strokeColor=#b85450;" value="var/jobs&#xa;Job-Status, Uploads, Logs, Outputs" vertex="1">
<mxGeometry height="134" width="215" x="1628" y="217" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-6" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" value="Preprocessing&#xa;src/preprocessing/exploration_preprocess.py" vertex="1">
<mxGeometry height="78" width="386" x="2060" y="88" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-7" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=#dae8fc;strokeColor=#6c8ebf;" value="Optimierung&#xa;src/optimization/run_optimization.py" vertex="1">
<mxGeometry height="78" width="333" x="3082" y="249" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-8" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=#e1d5e7;strokeColor=#9673a6;" value="Pyomo Modell&#xa;src/optimization/model_builder.py" vertex="1">
<mxGeometry height="78" width="312" x="3665" y="221" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-9" parent="1" style="whiteSpace=wrap;strokeWidth=2;fillColor=light-dark(#eeeeee,#1f2020);strokeColor=light-dark(#999999,#cccccc);fontColor=light-dark(#333333,#cccccc);" value="Solver&#xa;HiGHS / Gurobi / SCIP" vertex="1">
<mxGeometry height="78" width="219" x="4227" y="221" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-10" parent="1" style="shape=cylinder3;boundedLbl=1;backgroundOutline=1;size=10;strokeWidth=2;whiteSpace=wrap;fillColor=#f8cecc;strokeColor=#b85450;" value="processed Parquet&#xa;Job-Verzeichnis" vertex="1">
<mxGeometry height="103" width="146" x="2696" y="236" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-11" parent="1" style="shape=cylinder3;boundedLbl=1;backgroundOutline=1;size=10;strokeWidth=2;whiteSpace=wrap;fillColor=#f8cecc;strokeColor=#b85450;" value="output.xlsx&#xa;warmstart.json" vertex="1">
<mxGeometry height="100" width="124" x="3759" y="465" as="geometry" />
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-12" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-1" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.17;entryX=0;entryY=0.5;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-2" value="lädt Excel hoch">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="224" y="261" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-13" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-2" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.22;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-3" value="Datei auswählen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="588" y="261" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-14" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.66;exitY=0.01;entryX=0.36;entryY=0.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="POST /api/run&#xa;Excel + Parameter + optional Warmstart">
<mxGeometry relative="1" x="0.2387" y="-60" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="20" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-15" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-4" style="curved=1;startArrow=none;endArrow=block;exitX=0.37;exitY=1.01;entryX=0.65;entryY=1.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-3" value="job_id / Status / Daten / Downloads">
<mxGeometry relative="1" x="-0.3341" y="-88" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="600" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-16" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-4" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.07;entryX=0;entryY=0.31;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-5" value="Job-Verzeichnis, input.xlsx, job.json schreiben">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1503" y="228" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-17" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-5" style="curved=1;startArrow=none;endArrow=block;exitX=0.87;exitY=0;entryX=0;entryY=0.28;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-6" value="input.xlsx bereitstellen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1951" y="100" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-18" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-4" style="curved=1;startArrow=none;endArrow=block;exitX=0.75;exitY=0.01;entryX=0;entryY=0.64;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-6" value="Subprozess starten">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1503" y="143" />
<mxPoint x="1951" y="143" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-19" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-6" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0.06;entryY=0;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-10" value="processed/*.parquet schreiben">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="2571" y="127" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-20" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-4" style="curved=1;startArrow=none;endArrow=block;exitX=0.65;exitY=0.01;entryX=0.36;entryY=0;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-7" value="Subprozess starten">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1503" y="41" />
<mxPoint x="2962" y="41" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-21" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-10" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.5;entryX=0;entryY=0.5;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-7" value="processed/*.parquet lesen">
<mxGeometry relative="1" as="geometry">
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-22" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-7" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.13;entryX=0;entryY=0.34;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-8" value="Modell bauen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="3540" y="238" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-23" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-8" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.26;entryX=0;entryY=0.3;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-9" value="Optimierungsproblem senden">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="4102" y="226" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-24" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-9" style="curved=1;startArrow=none;endArrow=block;exitX=0;exitY=0.7;entryX=1;entryY=0.74;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-8" value="Lösung / Status zurückgeben">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="4102" y="294" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-25" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-8" style="curved=1;startArrow=none;endArrow=block;exitX=0.18;exitY=1;entryX=0.92;entryY=1;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-7" value="Lösung bereitstellen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="3540" y="369" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-26" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-7" style="curved=1;startArrow=none;endArrow=block;exitX=0.66;exitY=1;entryX=0;entryY=0.47;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-11" value="output.xlsx + warmstart.json schreiben">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="3540" y="501" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-27" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-7" style="curved=1;startArrow=none;endArrow=block;exitX=0.31;exitY=1;entryX=0.87;entryY=1;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-5" value="optimization.log schreiben">
<mxGeometry relative="1" x="-0.0008" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="2962" y="467" />
<mxPoint x="1951" y="467" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-28" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-4" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.51;entryX=0;entryY=0.54;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-5" value="job.json aktualisieren">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1503" y="296" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-29" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.71;exitY=0.01;entryX=0.31;entryY=0.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="GET /api/jobs/job_id">
<mxGeometry relative="1" x="0.1857" y="-49" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="88" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-30" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.78;exitY=0.01;entryX=0.24;entryY=0.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="GET /api/jobs/job_id/logs/name">
<mxGeometry relative="1" x="-0.1467" y="-38" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="144" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-31" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.06;entryX=0;entryY=0.05;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="GET /api/jobs/job_id/monthly-flows">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1000" y="224" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-32" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.58;entryX=0;entryY=0.63;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="GET /api/jobs/job_id/capacity-timeseries">
<mxGeometry relative="1" x="0.015" y="26" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="316" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-33" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.97;exitY=1.01;entryX=0.1;entryY=1.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="GET /api/jobs/job_id/output">
<mxGeometry relative="1" x="-0.1741" y="43" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="396" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-34" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.77;exitY=1.01;entryX=0.26;entryY=1.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="GET /api/jobs/job_id/warmstart">
<mxGeometry relative="1" x="-0.2589" y="69" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="464" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-35" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0.69;exitY=1.01;entryX=0.33;entryY=1.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="POST /api/jobs/job_id/cancel">
<mxGeometry relative="1" x="-0.3176" y="79" as="geometry">
<mxPoint as="offset" />
<Array as="points">
<mxPoint x="1000" y="532" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-36" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-4" style="curved=1;startArrow=none;endArrow=block;exitX=1;exitY=0.79;entryX=0;entryY=0.7;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-5" value="job.json / Logs lesen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1503" y="340" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-37" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-11" style="curved=1;startArrow=none;endArrow=block;exitX=0;exitY=0.6;entryX=0.65;entryY=1.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="output.xlsx / warmstart.json lesen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="3540" y="559" />
<mxPoint x="1503" y="559" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-38" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-10" style="curved=1;startArrow=none;endArrow=block;exitX=0;exitY=0.93;entryX=0.85;entryY=1.01;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-4" value="Kapazitaetsdaten lesen">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="2571" y="408" />
<mxPoint x="1503" y="408" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="LSBXT6lm1qTOuGE88BYh-39" edge="1" parent="1" source="LSBXT6lm1qTOuGE88BYh-3" style="curved=1;startArrow=none;endArrow=block;exitX=0;exitY=0.9;entryX=1;entryY=0.97;rounded=0;" target="LSBXT6lm1qTOuGE88BYh-1" value="zeigt Status, Logs, Diagramme, Downloadlinks">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="588" y="359" />
<mxPoint x="224" y="359" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

BIN
COALLOG_v0.1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

102
docs/app-kommunikation.md Normal file
View File

@ -0,0 +1,102 @@
# App-Kommunikation
Dieses Diagramm zeigt die Kommunikation zwischen Browser, FastAPI-Backend,
Job-Ablage und der Python-Optimierungspipeline.
```mermaid
flowchart LR
user[Benutzer im Browser]
frontend[React Frontend<br/>Vite Build / App.jsx]
backend[FastAPI Backend<br/>webapp/backend/main.py]
jobs[(var/jobs<br/>Job-Status, Uploads, Logs, Outputs)]
preprocess[Preprocessing<br/>src/preprocessing/exploration_preprocess.py]
optimization[Optimierung<br/>src/optimization/run_optimization.py]
model[Pyomo Modell<br/>src/optimization/model_builder.py]
solver[Solver<br/>HiGHS / Gurobi / SCIP]
processed[(processed Parquet<br/>Job-Verzeichnis)]
output[(output.xlsx<br/>warmstart.json)]
user --> frontend
frontend -->|POST /api/run<br/>Excel + Parameter + optional Warmstart| backend
backend -->|Job-Verzeichnis anlegen| jobs
backend -->|Subprozess starten| preprocess
preprocess -->|liest Excel<br/>schreibt vorbereitete Tabellen| processed
backend -->|Subprozess starten| optimization
optimization -->|lädt Tabellen| processed
optimization --> model
model --> solver
solver -->|Lösung| model
optimization -->|Excel + Warmstart exportieren| output
backend -->|Status completed / failed / cancelled| jobs
frontend -->|GET /api/jobs/{job_id}| backend
frontend -->|GET /api/jobs/{job_id}/logs/{name}| backend
frontend -->|GET /api/jobs/{job_id}/monthly-flows| backend
frontend -->|GET /api/jobs/{job_id}/capacity-timeseries| backend
frontend -->|GET /api/jobs/{job_id}/output| backend
frontend -->|GET /api/jobs/{job_id}/warmstart| backend
frontend -->|POST /api/jobs/{job_id}/cancel| backend
backend -->|liest job.json / Logs| jobs
backend -->|liest output.xlsx| output
backend -->|liest processed Parquet| processed
```
## Ablauf eines Optimierungslaufs
```mermaid
sequenceDiagram
participant U as Benutzer
participant F as React Frontend
participant B as FastAPI Backend
participant P as Preprocessing
participant O as Optimierung
participant S as Solver
participant V as var/jobs
U->>F: Excel-Datei und Solver-Parameter auswaehlen
F->>B: POST /api/run
B->>V: Job-Verzeichnis, input.xlsx, job.json anlegen
B-->>F: job_id zurueckgeben
B->>P: exploration_preprocess.py als Subprozess
P->>V: processed/*.parquet und preprocess.log schreiben
B->>O: run_optimization.py als Subprozess
O->>S: Pyomo-Modell loesen
S-->>O: Loesung / Status
O->>V: output.xlsx, warmstart.json, optimization.log schreiben
B->>V: job.json auf completed/failed/cancelled setzen
loop Polling waehrend der Laufzeit
F->>B: GET /api/jobs/{job_id}
B-->>F: Status
F->>B: GET /api/jobs/{job_id}/logs/optimization
B-->>F: Logtext
end
F->>B: GET /api/jobs/{job_id}/monthly-flows
B-->>F: Monatsfluesse fuer Plotly
F->>B: GET /api/jobs/{job_id}/capacity-timeseries
B-->>F: Kapazitaets-Zeitreihen fuer Plotly
F->>B: GET /api/jobs/{job_id}/output
B-->>F: output.xlsx Download
```
## Deployment-Sicht
```mermaid
flowchart TB
subgraph docker[Docker Container leag-coallog]
nginxless[FastAPI / Uvicorn<br/>Port 8080]
static[React Static Build<br/>webapp/frontend/dist]
python[Python Runtime<br/>Preprocessing + Optimierung]
gurobi[Gurobi / HiGHS / SCIP]
end
browser[Browser] -->|HTTP :8080| nginxless
nginxless -->|liefert statische Dateien| static
nginxless -->|API-Aufrufe /api/*| python
python --> gurobi
python --> volume[(Host Volume ./var:/app/var)]
license[(Host ./licenses:/app/licenses:ro<br/>oder GRB_WLS*)] --> gurobi
```

View File

@ -906,29 +906,6 @@ def build_model(
model.W, model.D, rule=lambda m, w, d: m.devV_W_pos[w, d] - m.devV_W_neg[w, d] == m.devV_W[w, d]
)
model.lambda_glatt = pyo.Param(initialize=1e5, mutable=True, within=pyo.NonNegativeReals)
model.m_ijwd = pyo.Expression(
model.I,
model.J,
model.W,
model.D,
rule=lambda m, i, j, w, d: (1 / len(m.S))
* sum(m.x[i, j, w, d, s] for s in m.S if (i, j, w, d, s) in m.x),
)
model.t_glatt = pyo.Var(model.I, model.J, model.W, model.D, model.S, domain=pyo.NonNegativeReals)
def glatt_hi(m, i, j, w, d, s):
return m.t_glatt[i, j, w, d, s] >= m.x[i, j, w, d, s] - m.m_ijwd[i, j, w, d]
def glatt_lo(m, i, j, w, d, s):
return m.t_glatt[i, j, w, d, s] >= -(m.x[i, j, w, d, s] - m.m_ijwd[i, j, w, d])
# Constraint: Upper absolute deviation from average shift flow (smoothness).
model.glatt_hi = pyo.Constraint(model.I, model.J, model.W, model.D, model.S, rule=glatt_hi)
# Constraint: Lower absolute deviation from average shift flow (smoothness).
model.glatt_lo = pyo.Constraint(model.I, model.J, model.W, model.D, model.S, rule=glatt_lo)
lambda_dev = 100_000
lambda_v = 100_000